/application/libraries/Datamapper.php
PHP | 6285 lines | 3068 code | 754 blank | 2463 comment | 535 complexity | 6355bf2589044f70b34e68a0c2f1c1bf MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- <?php
- /**
- * Data Mapper Class, OverZealous Edition
- *
- * Transforms database tables into objects.
- *
- * @license MIT License
- * @package DMZ
- * @category DMZ
- * @author Phil DeJarnett
- * @link http://www.overzealous.com/dmz/
- * @version 1.7.1 ($Rev: 395 $)
- */
- /**
- * Key for storing pre-converted classnames
- */
- define('DMZ_CLASSNAMES_KEY', '_dmz_classnames');
- /**
- * Data Mapper Class, OverZealous Edition
- *
- * Transforms database tables into objects.
- *
- * @package DMZ
- *
- * Properties (for code completion)
- * @property CI_DB_driver $db The CodeIgniter Database Library
- * @property CI_Loader $load The CodeIgnter Loader Library
- * @property CI_Language $lang The CodeIgniter Language Library
- * @property CI_Config $config The CodeIgniter Config Library
- * @property CI_Form_validation $form_validation The CodeIgniter Form Validation Library
- *
- *
- * Define some of the magic methods:
- *
- * Get By:
- * @method DataMapper get_by_id() get_by_id(int $value) Looks up an item by its ID.
- * @method DataMapper get_by_FIELD() get_by_FIELD(mixed $value) Looks up an item by a specific FIELD. Ex: get_by_name($user_name);
- * @method DataMapper get_by_related() get_by_related(mixed $related, string $field = NULL, string $value = NULL) Get results based on a related item.
- * @method DataMapper get_by_related_RELATEDFIELD() get_by_related_RELATEDFIELD(string $field = NULL, string $value = NULL) Get results based on a RELATEDFIELD. Ex: get_by_related_user('id', $userid);
- *
- * Save and Delete
- * @method DataMapper save_RELATEDFIELD() save_RELATEDFIELD(mixed $object) Saves relationship(s) using the specified RELATEDFIELD. Ex: save_user($user);
- * @method DataMapper delete_RELATEDFIELD() delete_RELATEDFIELD(mixed $object) Deletes relationship(s) using the specified RELATEDFIELD. Ex: delete_user($user);
- *
- * Related:
- * @method DataMapper where_related() where_related(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a related field.
- * @method DataMapper or_where_related() or_where_related(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a related field, via OR.
- * @method DataMapper where_in_related() where_in_related(mixed $related, string $field, array $values) Limits results by comparing a related field to a range of values.
- * @method DataMapper or_where_in_related() or_where_in_related(mixed $related, string $field, array $values) Limits results by comparing a related field to a range of values.
- * @method DataMapper where_not_in_related() where_not_in_related(mixed $related, string $field, array $values) Limits results by comparing a related field to a range of values.
- * @method DataMapper or_where_not_in_related() or_where_not_in_related(mixed $related, string $field, array $values) Limits results by comparing a related field to a range of values.
- * @method DataMapper like_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value.
- * @method DataMapper or_like_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value.
- * @method DataMapper not_like_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value.
- * @method DataMapper or_not_like_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value.
- * @method DataMapper ilike_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value (case insensitive).
- * @method DataMapper or_ilike_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value (case insensitive).
- * @method DataMapper not_ilike_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value (case insensitive).
- * @method DataMapper or_not_ilike_related() like_related(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a related field to a value (case insensitive).
- * @method DataMapper group_by_related() group_by_related(mixed $related, string $field) Groups the query by a related field.
- * @method DataMapper having_related() having_related(mixed $related, string $field, string $value) Groups the querying using a HAVING clause.
- * @method DataMapper or_having_related() having_related(mixed $related, string $field, string $value) Groups the querying using a HAVING clause, via OR.
- * @method DataMapper order_by_related() order_by_related(mixed $related, string $field, string $direction) Orders the query based on a related field.
- *
- *
- * Join Fields:
- * @method DataMapper where_join_field() where_join_field(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a join field.
- * @method DataMapper or_where_join_field() or_where_join_field(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a join field, via OR.
- * @method DataMapper where_in_join_field() where_in_join_field(mixed $related, string $field, array $values) Limits results by comparing a join field to a range of values.
- * @method DataMapper or_where_in_join_field() or_where_in_join_field(mixed $related, string $field, array $values) Limits results by comparing a join field to a range of values.
- * @method DataMapper where_not_in_join_field() where_not_in_join_field(mixed $related, string $field, array $values) Limits results by comparing a join field to a range of values.
- * @method DataMapper or_where_not_in_join_field() or_where_not_in_join_field(mixed $related, string $field, array $values) Limits results by comparing a join field to a range of values.
- * @method DataMapper like_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value.
- * @method DataMapper or_like_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value.
- * @method DataMapper not_like_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value.
- * @method DataMapper or_not_like_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value.
- * @method DataMapper ilike_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value (case insensitive).
- * @method DataMapper or_ilike_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value (case insensitive).
- * @method DataMapper not_ilike_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value (case insensitive).
- * @method DataMapper or_not_ilike_join_field() like_join_field(mixed $related, string $field, string $value, string $match = 'both') Limits results by matching a join field to a value (case insensitive).
- * @method DataMapper group_by_join_field() group_by_join_field(mixed $related, string $field) Groups the query by a join field.
- * @method DataMapper having_join_field() having_join_field(mixed $related, string $field, string $value) Groups the querying using a HAVING clause.
- * @method DataMapper or_having_join_field() having_join_field(mixed $related, string $field, string $value) Groups the querying using a HAVING clause, via OR.
- * @method DataMapper order_by_join_field() order_by_join_field(mixed $related, string $field, string $direction) Orders the query based on a join field.
- *
- * SQL Functions:
- * @method DataMapper select_func() select_func(string $function_name, mixed $args,..., string $alias) Selects the result of a SQL function. Alias is required.
- * @method DataMapper where_func() where_func(string $function_name, mixed $args,..., string $value) Limits results based on a SQL function.
- * @method DataMapper or_where_func() or_where_func(string $function_name, mixed $args,..., string $value) Limits results based on a SQL function, via OR.
- * @method DataMapper where_in_func() where_in_func(string $function_name, mixed $args,..., array $values) Limits results by comparing a SQL function to a range of values.
- * @method DataMapper or_where_in_func() or_where_in_func(string $function_name, mixed $args,..., array $values) Limits results by comparing a SQL function to a range of values.
- * @method DataMapper where_not_in_func() where_not_in_func(string $function_name, string $field, array $values) Limits results by comparing a SQL function to a range of values.
- * @method DataMapper or_where_not_in_func() or_where_not_in_func(string $function_name, mixed $args,..., array $values) Limits results by comparing a SQL function to a range of values.
- * @method DataMapper like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
- * @method DataMapper or_like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
- * @method DataMapper not_like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
- * @method DataMapper or_not_like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
- * @method DataMapper ilike_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value (case insensitive).
- * @method DataMapper or_ilike_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value (case insensitive).
- * @method DataMapper not_ilike_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value (case insensitive).
- * @method DataMapper or_not_ilike_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value (case insensitive).
- * @method DataMapper group_by_func() group_by_func(string $function_name, mixed $args,...) Groups the query by a SQL function.
- * @method DataMapper having_func() having_func(string $function_name, mixed $args,..., string $value) Groups the querying using a HAVING clause.
- * @method DataMapper or_having_func() having_func(string $function_name, mixed $args,..., string $value) Groups the querying using a HAVING clause, via OR.
- * @method DataMapper order_by_func() order_by_func(string $function_name, mixed $args,..., string $direction) Orders the query based on a SQL function.
- *
- * Field -> SQL functions:
- * @method DataMapper where_field_field_func() where_field_func($field, string $function_name, mixed $args,...) Limits results based on a SQL function.
- * @method DataMapper or_where_field_field_func() or_where_field_func($field, string $function_name, mixed $args,...) Limits results based on a SQL function, via OR.
- * @method DataMapper where_in_field_field_func() where_in_field_func($field, string $function_name, mixed $args,...) Limits results by comparing a SQL function to a range of values.
- * @method DataMapper or_where_in_field_field_func() or_where_in_field_func($field, string $function_name, mixed $args,...) Limits results by comparing a SQL function to a range of values.
- * @method DataMapper where_not_in_field_field_func() where_not_in_field_func($field, string $function_name, string $field) Limits results by comparing a SQL function to a range of values.
- * @method DataMapper or_where_not_in_field_field_func() or_where_not_in_field_func($field, string $function_name, mixed $args,...) Limits results by comparing a SQL function to a range of values.
- * @method DataMapper like_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value.
- * @method DataMapper or_like_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value.
- * @method DataMapper not_like_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value.
- * @method DataMapper or_not_like_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value.
- * @method DataMapper ilike_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value (case insensitive).
- * @method DataMapper or_ilike_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value (case insensitive).
- * @method DataMapper not_ilike_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value (case insensitive).
- * @method DataMapper or_not_ilike_field_field_func() like_field_func($field, string $function_name, mixed $args,...) Limits results by matching a SQL function to a value (case insensitive).
- * @method DataMapper group_by_field_field_func() group_by_field_func($field, string $function_name, mixed $args,...) Groups the query by a SQL function.
- * @method DataMapper having_field_field_func() having_field_func($field, string $function_name, mixed $args,...) Groups the querying using a HAVING clause.
- * @method DataMapper or_having_field_field_func() having_field_func($field, string $function_name, mixed $args,...) Groups the querying using a HAVING clause, via OR.
- * @method DataMapper order_by_field_field_func() order_by_field_func($field, string $function_name, mixed $args,...) Orders the query based on a SQL function.
- *
- * Subqueries:
- * @method DataMapper select_subquery() select_subquery(DataMapper $subquery, string $alias) Selects the result of a function. Alias is required.
- * @method DataMapper where_subquery() where_subquery(mixed $subquery_or_field, mixed $value_or_subquery) Limits results based on a subquery.
- * @method DataMapper or_where_subquery() or_where_subquery(mixed $subquery_or_field, mixed $value_or_subquery) Limits results based on a subquery, via OR.
- * @method DataMapper where_in_subquery() where_in_subquery(mixed $subquery_or_field, mixed $values_or_subquery) Limits results by comparing a subquery to a range of values.
- * @method DataMapper or_where_in_subquery() or_where_in_subquery(mixed $subquery_or_field, mixed $values_or_subquery) Limits results by comparing a subquery to a range of values.
- * @method DataMapper where_not_in_subquery() where_not_in_subquery(mixed $subquery_or_field, string $field, mixed $values_or_subquery) Limits results by comparing a subquery to a range of values.
- * @method DataMapper or_where_not_in_subquery() or_where_not_in_subquery(mixed $subquery_or_field, mixed $values_or_subquery) Limits results by comparing a subquery to a range of values.
- * @method DataMapper like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
- * @method DataMapper or_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
- * @method DataMapper not_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
- * @method DataMapper or_not_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
- * @method DataMapper ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive).
- * @method DataMapper or_ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive).
- * @method DataMapper not_ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive).
- * @method DataMapper or_not_ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive).
- * @method DataMapper having_subquery() having_subquery(string $field, DataMapper $subquery) Groups the querying using a HAVING clause.
- * @method DataMapper or_having_subquery() having_subquery(string $field, DataMapper $subquery) Groups the querying using a HAVING clause, via OR.
- * @method DataMapper order_by_subquery() order_by_subquery(DataMapper $subquery, string $direction) Orders the query based on a subquery.
- *
- * Related Subqueries:
- * @method DataMapper where_related_subquery() where_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results based on a subquery.
- * @method DataMapper or_where_related_subquery() or_where_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results based on a subquery, via OR.
- * @method DataMapper where_in_related_subquery() where_in_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results by comparing a subquery to a range of values.
- * @method DataMapper or_where_in_related_subquery() or_where_in_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results by comparing a subquery to a range of values.
- * @method DataMapper where_not_in_related_subquery() where_not_in_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results by comparing a subquery to a range of values.
- * @method DataMapper or_where_not_in_related_subquery() or_where_not_in_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results by comparing a subquery to a range of values.
- * @method DataMapper having_related_subquery() having_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Groups the querying using a HAVING clause.
- * @method DataMapper or_having_related_subquery() having_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Groups the querying using a HAVING clause, via OR.
- *
- * Array Extension:
- * @method array to_array() to_array($fields = '') NEEDS ARRAY EXTENSION. Converts this object into an associative array. @link DMZ_Array::to_array
- * @method array all_to_array() all_to_array($fields = '') NEEDS ARRAY EXTENSION. Converts the all array into an associative array. @link DMZ_Array::all_to_array
- * @method array|bool from_array() from_array($data, $fields = '', $save = FALSE) NEEDS ARRAY EXTENSION. Converts $this->all into an associative array. @link DMZ_Array::all_to_array
- *
- * CSV Extension
- * @method bool csv_export() csv_export($filename, $fields = '', $include_header = TRUE) NEEDS CSV EXTENSION. Exports this object as a CSV file.
- * @method array csv_import() csv_import($filename, $fields = '', $header_row = TRUE, $callback = NULL) NEEDS CSV EXTENSION. Imports a CSV file into this object.
- *
- * JSON Extension:
- * @method string to_json() to_json($fields = '', $pretty_print = FALSE) NEEDS JSON EXTENSION. Converts this object into a JSON string.
- * @method string all_to_json() all_to_json($fields = '', $pretty_print = FALSE) NEEDS JSON EXTENSION. Converts the all array into a JSON string.
- * @method bool from_json() from_json($json, $fields = '') NEEDS JSON EXTENSION. Imports the values from a JSON string into this object.
- * @method void set_json_content_type() set_json_content_type() NEEDS JSON EXTENSION. Sets the content type header to Content-Type: application/json.
- *
- * SimpleCache Extension:
- * @method DataMapper get_cached() get_cached($limit = '', $offset = '') NEEDS SIMPLECACHE EXTENSION. Enables cacheable queries.
- * @method DataMapper clear_cache() get_cached($segment,...) NEEDS SIMPLECACHE EXTENSION. Clears a cache for the specfied segment.
- *
- */
- class DataMapper implements IteratorAggregate {
- /**
- * Stores the shared configuration
- * @var array
- */
- static $config = array();
- /**
- * Stores settings that are common across a specific Model
- * @var array
- */
- static $common = array(DMZ_CLASSNAMES_KEY => array());
- /**
- * Stores global extensions
- * @var array
- */
- static $global_extensions = array();
- /**
- * Used to override unset default properties.
- * @var array
- */
- static $_dmz_config_defaults = array(
- 'timestamp_format' => 'Y-m-d H:i:s O',
- 'created_field' => 'created',
- 'updated_field' => 'updated',
- 'extensions_path' => 'datamapper',
- 'field_label_lang_format' => '${model}_${field}',
- );
- /**
- * Contains any errors that occur during validation, saving, or other
- * database access.
- * @var DM_Error_Object
- */
- public $error;
- /**
- * Used to keep track of the original values from the database, to
- * prevent unecessarily changing fields.
- * @var object
- */
- public $stored;
- /**
- * DB Table Prefix
- * @var string
- */
- public $prefix = '';
- /**
- * DB Join Table Prefix
- * @var string
- */
- public $join_prefix = '';
- /**
- * The name of the table for this model (may be automatically generated
- * from the classname).
- * @var string
- */
- public $table = '';
- /**
- * The singular name for this model (may be automatically generated from
- * the classname).
- * @var string
- */
- public $model = '';
- /**
- * Can be used to override the default database behavior.
- * @var mixed
- */
- public $db_params = '';
- /**
- * Prefix string used when reporting errors.
- * @var string
- */
- public $error_prefix = '';
- /**
- * Suffic string used when reporting errors.
- * @var string
- */
- public $error_suffix = '';
- /**
- * Custom name for the automatic timestamp saved with new objects.
- * Defaults to 'created'.
- * @var string
- */
- public $created_field = '';
- /**
- * Custom name for the automatic timestamp saved when an object changes.
- * Defaults to 'updated'.
- * @var string
- */
- public $updated_field = '';
- /**
- * If TRUE, automatically wrap every save and delete in a transaction.
- * @var bool
- */
- public $auto_transaction = FALSE;
- /**
- * If TRUE, has_many relationships are automatically loaded when accessed.
- * Not recommended in most situations.
- * @var bool
- */
- public $auto_populate_has_many = FALSE;
- /**
- * If TRUE, has_one relationships are automatically loaded when accessed.
- * Not recommended in some situations.
- * @var bool
- */
- public $auto_populate_has_one = FALSE;
- /**
- * Enables the old method of storing the all array using an object's ID.
- * @var bool
- */
- public $all_array_uses_ids = FALSE;
- /**
- * The result of validate is stored here.
- * @var bool
- */
- public $valid = FALSE;
- /**
- * If TRUE, the created/updated fields are stored using local time.
- * If FALSE (the default), they are stored using UTC
- * @var bool
- */
- public $local_time = FALSE;
- /**
- * If TRUE, the created/updated fields are stored as a unix timestamp,
- * as opposed to a formatted string.
- * Defaults to FALSE.
- * @var bool
- */
- public $unix_timestamp = FALSE;
- /**
- * Set to a date format to override the default format of
- * 'Y-m-d H:i:s O'
- * @var string
- */
- public $timestamp_format = '';
- /**
- * Contains the database fields for this object.
- * ** Automatically configured **
- * @var array
- */
- public $fields = array();
- /**
- * Set to a string to use when autoloading lang files.
- * Can contain two magic values: ${model} and ${table}.
- * These are automatically
- * replaced when looking up the language file.
- * Defaults to model_${model}
- * @var string
- */
- public $lang_file_format = '';
- /**
- * Set to a string to use when looking up field labels. Can contain three
- * magic values: ${model}, ${table}, and ${field}. These are automatically
- * replaced when looking up the language file.
- * Defaults to ${model}_${field}
- * @var string
- */
- public $field_label_lang_format = '';
- /**
- * Contains the result of the last query.
- * @var array
- */
- public $all = array();
- /**
- * Semi-private field used to track the parent model/id if there is one.
- * @var array
- */
- public $parent = array();
- /**
- * Contains the validation rules, label, and get_rules for each field.
- * @var array
- */
- public $validation = array();
- /**
- * Contains any related objects of which this model is related one or more times.
- * @var array
- */
- public $has_many = array();
- /**
- * Contains any related objects of which this model is singularly related.
- * @var array
- */
- public $has_one = array();
- /**
- * Used to enable or disable the production cache.
- * This should really only be set in the global configuration.
- * @var bool
- */
- public $production_cache = FALSE;
- /**
- * Used to determine where to look for extensions.
- * This should really only be set in the global configuration.
- * @var string
- */
- public $extensions_path = '';
- /**
- * If set to an array of names, this will automatically load the
- * specified extensions for this model.
- * @var mixed
- */
- public $extensions = NULL;
- /**
- * If a query returns more than the number of rows specified here,
- * then it will be automatically freed after a get.
- * @var int
- */
- public $free_result_threshold = 100;
- /**
- * This can be specified as an array of fields to sort by if no other
- * sorting or selection has occurred.
- * @var mixed
- */
- public $default_order_by = NULL;
- // tracks whether or not the object has already been validated
- protected $_validated = FALSE;
- // Tracks the columns that need to be instantiated after a GET
- protected $_instantiations = NULL;
- // Tracks get_rules, matches, and intval rules, to spped up _to_object
- protected $_field_tracking = NULL;
- // used to track related queries in deep relationships.
- protected $_query_related = array();
- // If true before a related get(), any extra fields on the join table will be added.
- protected $_include_join_fields = FALSE;
- // If true before a save, this will force the next save to be new.
- protected $_force_save_as_new = FALSE;
- // If true, the next where statement will not be prefixed with an AND or OR.
- protected $_where_group_started = FALSE;
- /**
- * Constructor
- *
- * Initialize DataMapper.
- * @param int $id if provided, load in the object specified by that ID.
- */
- public function DataMapper($id = NULL)
- {
- $this->_dmz_assign_libraries();
- $this_class = strtolower(get_class($this));
- $is_dmz = $this_class == 'datamapper';
- if($is_dmz)
- {
- $this->_load_languages();
- $this->_load_helpers();
- }
- // this is to ensure that singular is only called once per model
- if(isset(DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class])) {
- $common_key = DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class];
- } else {
- DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class] = $common_key = singular($this_class);
- }
-
- // Determine model name
- if (empty($this->model))
- {
- $this->model = $common_key;
- }
- // Load stored config settings by reference
- foreach (DataMapper::$config as $config_key => &$config_value)
- {
- // Only if they're not already set
- if (empty($this->{$config_key}))
- {
- $this->{$config_key} =& $config_value;
- }
- }
- // Load model settings if not in common storage
- if ( ! isset(DataMapper::$common[$common_key]))
- {
- // If model is 'datamapper' then this is the initial autoload by CodeIgniter
- if ($is_dmz)
- {
- // Load config settings
- $this->config->load('datamapper', TRUE, TRUE);
- // Get and store config settings
- DataMapper::$config = $this->config->item('datamapper');
- // now double check that all required config values were set
- foreach(DataMapper::$_dmz_config_defaults as $config_key => $config_value)
- {
- if(empty(DataMapper::$config[$config_key]))
- {
- DataMapper::$config[$config_key] = $config_value;
- }
- }
-
- DataMapper::_load_extensions(DataMapper::$global_extensions, DataMapper::$config['extensions']);
- unset(DataMapper::$config['extensions']);
- return;
- }
- // load language file, if requested and it exists
- if(!empty($this->lang_file_format))
- {
- $lang_file = str_replace(array('${model}', '${table}'), array($this->model, $this->table), $this->lang_file_format);
- $deft_lang = $this->config->item('language');
- $idiom = ($deft_lang == '') ? 'english' : $deft_lang;
- if(file_exists(APPPATH.'language/'.$idiom.'/'.$lang_file.'_lang'.EXT))
- {
- $this->lang->load($lang_file, $idiom);
- }
- }
-
- $loaded_from_cache = FALSE;
-
- // Load in the production cache for this model, if it exists
- if( ! empty(DataMapper::$config['production_cache']))
- {
- // attempt to load the production cache file
- $cache_folder = APPPATH . DataMapper::$config['production_cache'];
- if(file_exists($cache_folder) && is_dir($cache_folder) && is_writeable($cache_folder))
- {
- $cache_file = $cache_folder . '/' . $common_key . EXT;
- if(file_exists($cache_file))
- {
- include($cache_file);
- if(isset($cache))
- {
- DataMapper::$common[$common_key] =& $cache;
- unset($cache);
-
- // allow subclasses to add initializations
- if(method_exists($this, 'post_model_init'))
- {
- $this->post_model_init(TRUE);
- }
-
- // Load extensions (they are not cacheable)
- $this->_initiate_local_extensions($common_key);
-
- $loaded_from_cache = TRUE;
- }
- }
- }
- }
-
- if(! $loaded_from_cache)
- {
- // Determine table name
- if (empty($this->table))
- {
- $this->table = plural(get_class($this));
- }
-
- // Add prefix to table
- $this->table = $this->prefix . $this->table;
- $this->_field_tracking = array(
- 'get_rules' => array(),
- 'matches' => array(),
- 'intval' => array('id')
- );
-
- // Convert validation into associative array by field name
- $associative_validation = array();
-
- foreach ($this->validation as $name => $validation)
- {
- if(is_string($name)) {
- $validation['field'] = $name;
- } else {
- $name = $validation['field'];
- }
-
- // clean up possibly missing fields
- if( ! isset($validation['rules']))
- {
- $validation['rules'] = array();
- }
-
- // Populate associative validation array
- $associative_validation[$name] = $validation;
- if (!empty($validation['get_rules']))
- {
- $this->_field_tracking['get_rules'][] = $name;
- }
- // Check if there is a "matches" validation rule
- if (isset($validation['rules']['matches']))
- {
- $this->_field_tracking['matches'][$name] = $validation['rules']['matches'];
- }
- }
-
- // set up id column, if not set
- if(!isset($associative_validation['id']))
- {
- // label is set below, to prevent caching language-based labels
- $associative_validation['id'] = array(
- 'field' => 'id',
- 'rules' => array('integer')
- );
- }
-
- $this->validation = $associative_validation;
- // Force all other has_one ITFKs to integers on get
- foreach($this->has_one as $related => $rel_props)
- {
- $field = $related . '_id';
- if( in_array($field, $this->fields) &&
- ( ! isset($this->validation[$field]) || // does not have a validation key or...
- ! isset($this->validation[$field]['get_rules'])) && // a get_rules key...
- ( ! isset($this->validation[$related]) || // nor does the related have a validation key or...
- ! isset($this->validation[$related]['get_rules'])) ) // a get_rules key
- {
- // assume an int
- $this->_field_tracking['intval'][] = $field;
- }
- }
-
- // Get and store the table's field names and meta data
- $fields = $this->db->field_data($this->table);
-
- // Store only the field names and ensure validation list includes all fields
- foreach ($fields as $field)
- {
- // Populate fields array
- $this->fields[] = $field->name;
-
- // Add validation if current field has none
- if ( ! isset($this->validation[$field->name]))
- {
- // label is set below, to prevent caching language-based labels
- $this->validation[$field->name] = array('field' => $field->name, 'rules' => array());
- }
- }
-
- // convert simple has_one and has_many arrays into more advanced ones
- foreach(array('has_one', 'has_many') as $arr)
- {
- $new = array();
- foreach ($this->{$arr} as $related_field => $rel_props)
- {
- // allow for simple (old-style) associations
- if (is_int($related_field))
- {
- $related_field = $rel_props;
- }
- // convert value into array if necessary
- if ( ! is_array($rel_props))
- {
- $rel_props = array('class' => $rel_props);
- } else if ( ! isset($rel_props['class']))
- {
- // if already an array, ensure that the class attribute is set
- $rel_props['class'] = $related_field;
- }
- if( ! isset($rel_props['other_field']))
- {
- // add this model as the model to use in queries if not set
- $rel_props['other_field'] = $this->model;
- }
- if( ! isset($rel_props['join_self_as']))
- {
- // add this model as the model to use in queries if not set
- $rel_props['join_self_as'] = $rel_props['other_field'];
- }
- if( ! isset($rel_props['join_other_as']))
- {
- // add the key as the model to use in queries if not set
- $rel_props['join_other_as'] = $related_field;
- }
- $new[$related_field] = $rel_props;
- // load in labels for each not-already-set field
- if(!isset($this->validation[$related_field]))
- {
- $label = $this->localize_label($related_field);
- if(!empty($label))
- {
- // label is re-set below, to prevent caching language-based labels
- $this->validation[$related_field] = array('field' => $related_field, 'rules' => array());
- }
- }
- }
- // replace the old array
- $this->{$arr} = $new;
- }
-
- // allow subclasses to add initializations
- if(method_exists($this, 'post_model_init'))
- {
- $this->post_model_init(FALSE);
- }
-
- // Store common model settings
- foreach (array('table', 'fields', 'validation',
- 'has_one', 'has_many', '_field_tracking') as $item)
- {
- DataMapper::$common[$common_key][$item] = $this->{$item};
- }
-
- // if requested, store the item to the production cache
- if( ! empty(DataMapper::$config['production_cache']))
- {
- // attempt to load the production cache file
- $cache_folder = APPPATH . DataMapper::$config['production_cache'];
- if(file_exists($cache_folder) && is_dir($cache_folder) && is_writeable($cache_folder))
- {
- $cache_file = $cache_folder . '/' . $common_key . EXT;
- $cache = "<"."?php if ( ! defined('BASEPATH')) exit('No direct script access allowed'); \n";
-
- $cache .= '$cache = ' . var_export(DataMapper::$common[$common_key], TRUE) . ';';
-
- if ( ! $fp = @fopen($cache_file, 'w'))
- {
- show_error('Error creating production cache file: ' . $cache_file);
- }
-
- flock($fp, LOCK_EX);
- fwrite($fp, $cache);
- flock($fp, LOCK_UN);
- fclose($fp);
-
- @chmod($cache_file, FILE_WRITE_MODE);
- }
- }
-
- // Load extensions last, so they aren't cached.
- $this->_initiate_local_extensions($common_key);
- }
- // Finally, localize the labels here (because they shouldn't be cached
- // This also sets any missing labels.
- $validation =& DataMapper::$common[$common_key]['validation'];
- foreach($validation as $field => &$val)
- {
- // Localize label if necessary
- $val['label'] = $this->localize_label($field,
- isset($val['label']) ?
- $val['label'] :
- FALSE);
- }
- unset($validation);
- }
- // Load stored common model settings by reference
- foreach(DataMapper::$common[$common_key] as $key => &$value)
- {
- $this->{$key} =& $value;
- }
- // Clear object properties to set at default values
- $this->clear();
-
- if( ! empty($id) && is_numeric($id))
- {
- $this->get_by_id(intval($id));
- }
- }
- // --------------------------------------------------------------------
- /**
- * Reloads in the configuration data for a model. This is mainly
- * used to handle language changes. Only this instance and new instances
- * will see the changes.
- */
- public function reinitialize_model()
- {
- // this is to ensure that singular is only called once per model
- if(isset(DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class])) {
- $common_key = DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class];
- } else {
- DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class] = $common_key = singular($this_class);
- }
- unset(DataMapper::$common[$common_key]);
- $model = get_class($this);
- new $model(); // re-initialze
-
- // Load stored common model settings by reference
- foreach(DataMapper::$common[$common_key] as $key => &$value)
- {
- $this->{$key} =& $value;
- }
- }
- // --------------------------------------------------------------------
- /**
- * Autoload
- *
- * Autoloads object classes that are used with DataMapper.
- * This method will look in any model directories available to CI.
- *
- * Note:
- * It is important that they are autoloaded as loading them manually with
- * CodeIgniter's loader class will cause DataMapper's __get and __set functions
- * to not function.
- *
- * @param string $class Name of class to load.
- */
- public static function autoload($class)
- {
- // Don't attempt to autoload CI_ or MY_ prefixed classes
- if (in_array(substr($class, 0, 3), array('CI_', 'EE_', 'MY_')))
- {
- return;
- }
- // Prepare class
- $class = strtolower($class);
- $CI =& get_instance();
- // Prepare path
- if (isset($CI->load->_ci_model_paths) && is_array($CI->load->_ci_model_paths))
- {
- // use CI loader's model path
- $paths = $CI->load->_ci_model_paths;
- }
- else
- {
- $paths = array(APPPATH);
- }
- foreach ($paths as $path)
- {
- // Prepare file
- $file = $path . 'models/' . $class . EXT;
- // Check if file exists, require_once if it does
- if (file_exists($file))
- {
- require_once($file);
- break;
- }
- }
- // if class not loaded, do a recursive search of model paths for the class
- if (! class_exists($class))
- {
- foreach($paths as $path)
- {
- $found = DataMapper::recursive_require_once($class, $path . 'models');
- if($found)
- {
- break;
- }
- }
- }
- }
- // --------------------------------------------------------------------
- /**
- * Recursive Require Once
- *
- * Recursively searches the path for the class, require_once if found.
- *
- * @param string $class Name of class to look for
- * @param string $path Current path to search
- */
- protected static function recursive_require_once($class, $path)
- {
- $found = FALSE;
- $handle = opendir($path);
- if ($handle)
- {
- while (FALSE !== ($dir = readdir($handle)))
- {
- // If dir does not contain a dot
- if (strpos($dir, '.') === FALSE)
- {
- // Prepare recursive path
- $recursive_path = $path . '/' . $dir;
- // Prepare file
- $file = $recursive_path . '/' . $class . EXT;
- // Check if file exists, require_once if it does
- if (file_exists($file))
- {
- require_once($file);
- $found = TRUE;
- break;
- }
- else if (is_dir($recursive_path))
- {
- // Do a recursive search of the path for the class
- DataMapper::recursive_require_once($class, $recursive_path);
- }
- }
- }
- closedir($handle);
- }
- return $found;
- }
-
- // --------------------------------------------------------------------
-
- /**
- * Loads in any extensions used by this class or globally.
- *
- * @param array $extensions List of extensions to add to.
- * @param array $name List of new extensions to load.
- */
- protected static function _load_extensions(&$extensions, $names)
- {
- $CI =& get_instance();
- $class_prefixes = array(
- 0 => 'DMZ_',
- 1 => 'DataMapper_',
- 2 => $CI->config->item('subclass_prefix'),
- 3 => 'CI_'
- );
- foreach($names as $name => $options)
- {
- if( ! is_string($name))
- {
- $name = $options;
- $options = NULL;
- }
- // only load an extension if it wasn't already loaded in this context
- if(isset($extensions[$name]))
- {
- return;
- }
-
- if( ! isset($extensions['_methods']))
- {
- $extensions['_methods'] = array();
- }
-
- // determine the file name and class name
- if(strpos($name, '/') === FALSE)
- {
- $file = APPPATH . DataMapper::$config['extensions_path'] . '/' . $name . EXT;
- $ext = $name;
- }
- else
- {
- $file = APPPATH . $name . EXT;
- $ext = array_pop(explode('/', $name));
- }
-
- if(!file_exists($file))
- {
- show_error('DataMapper Error: loading extension ' . $name . ': File not found.');
- }
-
- // load class
- include_once($file);
-
- // Allow for DMZ_Extension, DataMapper_Extension, etc.
- foreach($class_prefixes as $index => $prefix)
- {
- if(class_exists($prefix.$ext))
- {
- if($index == 2) // "MY_"
- {
- // Load in the library this class is based on
- $CI->load->libary($ext);
- }
- $ext = $prefix.$ext;
- break;
- }
- }
- if(!class_exists($ext))
- {
- show_error("DataMapper Error: Unable to find a class for extension $name.");
- }
- // create class
- if(is_null($options))
- {
- $o = new $ext();
- }
- else
- {
- $o = new $ext($options);
- }
- $extensions[$name] = $o;
-
- // figure out which methods can be called on this class.
- $methods = get_class_methods($ext);
- foreach($methods as $m)
- {
- // do not load private methods or methods already loaded.
- if($m[0] !== '_' &&
- is_callable(array($o, $m)) &&
- ! isset($extensions['_methods'][$m])
- ) {
- // store this method.
- $extensions['_methods'][$m] = $name;
- }
- }
- }
- }
-
- // --------------------------------------------------------------------
- /**
- * Loads the extensions that are local to this model.
- * @param string $common_key Shared key to save extenions to.
- */
- private function _initiate_local_extensions($common_key)
- {
- if(!empty($this->extensions))
- {
- $extensions = $this->extensions;
- $this->extensions = array();
- DataMapper::_load_extensions($this->extensions, $extensions);
- }
- else
- {
- // ensure an empty array
- $this->extensions = array('_methods' => array());
- }
- // bind to the shared key, for dynamic loading
- DataMapper::$common[$common_key]['extensions'] =& $this->extensions;
- }
- // --------------------------------------------------------------------
-
- /**
- * Dynamically load an extension when needed.
- * @param object $name Name of the extension (or array of extensions).
- * @param array $options Options for the extension
- * @param boolean $local If TRUE, only loads the extension into this object
- */
- public function load_extension($name, $options = NULL, $local = FALSE)
- {
- if( ! is_array($name))
- {
- if( ! is_null($options))
- {
- $name = array($name => $options);
- }
- else
- {
- $name = array($name);
- }
- }
- // called individually to ensure that the array is modified directly
- // (and not copied instead)
- if($local)
- {
- DataMapper::_load_extensions($this->extensions, $name);
- }
- else
- {
- DataMapper::_load_extensions(DataMapper::$global_extensions, $name);
- }
-
- }
- // --------------------------------------------------------------------
-
-
- /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
- * *
- * Magic methods *
- * *
- * The following are methods to override the default PHP behaviour. *
- * *
- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
- // --------------------------------------------------------------------
- /**
- * Magic Get
- *
- * Returns the value of the named property.
- * If named property is a related item, instantiate it first.
- *
- * This method also instantiates the DB object and the form_validation
- * objects as necessary
- *
- * @ignore
- * @param string $name Name of property to look for
- * @return mixed
- */
- public function __get($name)
- {
- // We dynamically get DB when needed, and create a copy.
- // This allows multiple queries to be generated at the same time.
- if($name == 'db')
- {
- $CI =& get_instance();
- if($this->db_params === FALSE)
- {
- $this->db =& $CI->db;
- }
- else
- {
- if($this->db_params == '' || $this->db_params === TRUE)
- {
- // ensure the shared DB is disconnected, even if the app exits uncleanly
- if(!isset($CI->db->_has_shutdown_hook))
- {
- register_shutdown_function(array($CI->db, 'close'));
- $CI->db->_has_shutdown_hook = TRUE;
- }
- // clone, so we don't create additional connections to the DB
- $this->db = clone($CI->db);
- $this->db->_reset_select();
- }
- else
- {
- // connecting to a different database, so we *must* create additional copies.
- // It is up to the developer to close the connection!
- $this->db = $CI->load->database($this->db_params, TRUE, TRUE);
- }
- // these items are shared (for debugging)
- if(isset($CI->db))
- {
- $this->db->queries =& $CI->db->queries;
- $this->db->query_times =& $CI->db->query_times;
- }
- }
- // ensure the created DB is disconnected, even if the app exits uncleanly
- if(!isset($this->db->_has_shutdown_hook))
- {
- register_shutdown_function(array($this->db, 'close'));
- $this->db->_has_shutdown_hook = TRUE;
- }
- return $this->db;
- }
-
- // Special case to get form_validation when first accessed
- if($name == 'form_validation')
- {
- $CI =& get_instance();
- if( ! isset($CI->form_validation))
- {
- $CI->load->library('form_validation');
- }
- $this->form_validation = $CI->form_validation;
- $this->lang->load('form_validation');
- return $this->form_validation;
- }
- $has_many = isset($this->has_many[$name]);
- $has_one = isset($this->has_one[$name]);
- // If named property is a "has many" or "has one" related item
- if ($has_many || $has_one)
- {
- $related_properties = $has_many ? $this->has_many[$name] : $this->has_one[$name];
- // Instantiate it before accessing
- $class = $related_properties['class'];
- $this->{$name} = new $class();
- // Store parent data
- $this->{$name}->parent = array('model' => $related_properties['other_field'], 'id' => $this->id);
- // Check if Auto Populate for "has many" or "has one" is on
- // (but only if this object exists in the DB, and we aren't instantiating)
- if ($this->exists() &&
- ($has_many && $this->auto_populate_has_many) || ($has_one && $this->auto_populate_has_one))
- {
- $this->{$name}->get();
- }
- return $this->{$name};
- }
-
- $name_single = singular($name);
- if($name_single !== $name) {
- // possibly return single form of name
- $test = $this->{$name_single};
- if(is_object($test)) {
- return $test;
- }
- }
- return NULL;
- }
- // --------------------------------------------------------------------
- /**
- * Used several places to temporarily override the auto_populate setting
- * @ignore
- * @param string $related Related Name
- * @return DataMapper|NULL
- */
- private function &_get_without_auto_populating($related)
- {
- $b_many = $this->auto_populate_has_many;
- $b_one = $this->auto_populate_has_one;
- $this->auto_populate_has_many = FALSE;
- $this->auto_populate_has_one = FALSE;
- $ret =& $this->{$related};
- $this->auto_populate_has_many = $b_many;
- $this->auto_populate_has_one = $b_one;
- return $ret;
- }
- // --------------------------------------------------------------------
- /**
- * Magic Call
- *
- * Calls special methods, or extension methods.
- *
- * @ignore
- * @param string $method Method name
- * @param array $arguments Arguments to method
- * @return mixed
- */
- public function __call($method, $arguments)
- {
-
- // List of watched method names
- // NOTE: order matters: make sure more specific items are listed before
- // less specific items
- $watched_methods = array(
- 'save_', 'delete_',
- 'get_by_related_', 'get_by_related', 'get_by_',
- '_related_subquery', '_subquery',
- '_related_', '_related',
- '_join_field',
- '_field_func', '_func'
- );
- foreach ($watched_methods as $watched_method)
- {
- // See if called method is a watched method
- if (strpos($method, $watched_method) !== FALSE)
- {
- $pieces = explode($watched_method, $method);
- if ( ! empty($pieces[0]) && ! empty($pieces[1]))
- {
- // Watched method is in the middle
- return $this->{'_' . trim($watched_method, '_')}($pieces[0], array_merge(array($pieces[1]), $arguments));
- }
- else
- {
- // Watched method is a prefix or suffix
- return $this->{'_' . trim($watched_method, '_')}(str_replace($watched_method, '', $method), $ar…
Large files files are truncated, but you can click here to view the full file