PageRenderTime 69ms CodeModel.GetById 33ms RepoModel.GetById 0ms app.codeStats 1ms

/application/libraries/datamapper.php

https://bitbucket.org/nwd/nwd-base
PHP | 6488 lines | 3211 code | 770 blank | 2507 comment | 558 complexity | 3d2e809af0bb6c226016cb51271dd9af MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Data Mapper ORM Class
  4. *
  5. * Transforms database tables into objects.
  6. *
  7. * @license MIT License
  8. * @package DataMapper ORM
  9. * @category DataMapper ORM
  10. * @author Harro "WanWizard" Verton, James Wardlaw
  11. * @author Phil DeJarnett (up to v1.7.1.)
  12. * @link http://datamapper.exitecms.org/
  13. * @version 1.8.dev
  14. */
  15. /**
  16. * Key for storing pre-converted classnames
  17. */
  18. define('DMZ_CLASSNAMES_KEY', '_dmz_classnames');
  19. /**
  20. * DMZ version
  21. */
  22. define('DMZ_VERSION', '1.8.0');
  23. /**
  24. * Data Mapper Class
  25. *
  26. * Transforms database tables into objects.
  27. *
  28. * @package DataMapper ORM
  29. *
  30. * Properties (for code completion)
  31. * @property CI_DB_driver $db The CodeIgniter Database Library
  32. * @property CI_Loader $load The CodeIgnter Loader Library
  33. * @property CI_Language $lang The CodeIgniter Language Library
  34. * @property CI_Config $config The CodeIgniter Config Library
  35. * @property CI_Form_validation $form_validation The CodeIgniter Form Validation Library
  36. *
  37. *
  38. * Define some of the magic methods:
  39. *
  40. * Get By:
  41. * @method DataMapper get_by_id() get_by_id(int $value) Looks up an item by its ID.
  42. * @method DataMapper get_by_FIELD() get_by_FIELD(mixed $value) Looks up an item by a specific FIELD. Ex: get_by_name($user_name);
  43. * @method DataMapper get_by_related() get_by_related(mixed $related, string $field = NULL, string $value = NULL) Get results based on a related item.
  44. * @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);
  45. *
  46. * Save and Delete
  47. * @method DataMapper save_RELATEDFIELD() save_RELATEDFIELD(mixed $object) Saves relationship(s) using the specified RELATEDFIELD. Ex: save_user($user);
  48. * @method DataMapper delete_RELATEDFIELD() delete_RELATEDFIELD(mixed $object) Deletes relationship(s) using the specified RELATEDFIELD. Ex: delete_user($user);
  49. *
  50. * Related:
  51. * @method DataMapper where_related() where_related(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a related field.
  52. * @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.
  53. * @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.
  54. * @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.
  55. * @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.
  56. * @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.
  57. * @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.
  58. * @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.
  59. * @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.
  60. * @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.
  61. * @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).
  62. * @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).
  63. * @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).
  64. * @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).
  65. * @method DataMapper group_by_related() group_by_related(mixed $related, string $field) Groups the query by a related field.
  66. * @method DataMapper having_related() having_related(mixed $related, string $field, string $value) Groups the querying using a HAVING clause.
  67. * @method DataMapper or_having_related() having_related(mixed $related, string $field, string $value) Groups the querying using a HAVING clause, via OR.
  68. * @method DataMapper order_by_related() order_by_related(mixed $related, string $field, string $direction) Orders the query based on a related field.
  69. *
  70. *
  71. * Join Fields:
  72. * @method DataMapper where_join_field() where_join_field(mixed $related, string $field = NULL, string $value = NULL) Limits results based on a join field.
  73. * @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.
  74. * @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.
  75. * @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.
  76. * @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.
  77. * @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.
  78. * @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.
  79. * @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.
  80. * @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.
  81. * @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.
  82. * @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).
  83. * @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).
  84. * @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).
  85. * @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).
  86. * @method DataMapper group_by_join_field() group_by_join_field(mixed $related, string $field) Groups the query by a join field.
  87. * @method DataMapper having_join_field() having_join_field(mixed $related, string $field, string $value) Groups the querying using a HAVING clause.
  88. * @method DataMapper or_having_join_field() having_join_field(mixed $related, string $field, string $value) Groups the querying using a HAVING clause, via OR.
  89. * @method DataMapper order_by_join_field() order_by_join_field(mixed $related, string $field, string $direction) Orders the query based on a join field.
  90. *
  91. * SQL Functions:
  92. * @method DataMapper select_func() select_func(string $function_name, mixed $args,..., string $alias) Selects the result of a SQL function. Alias is required.
  93. * @method DataMapper where_func() where_func(string $function_name, mixed $args,..., string $value) Limits results based on a SQL function.
  94. * @method DataMapper or_where_func() or_where_func(string $function_name, mixed $args,..., string $value) Limits results based on a SQL function, via OR.
  95. * @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.
  96. * @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.
  97. * @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.
  98. * @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.
  99. * @method DataMapper like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
  100. * @method DataMapper or_like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
  101. * @method DataMapper not_like_func() like_func(string $function_name, mixed $args,..., string $value) Limits results by matching a SQL function to a value.
  102. * @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.
  103. * @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).
  104. * @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).
  105. * @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).
  106. * @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).
  107. * @method DataMapper group_by_func() group_by_func(string $function_name, mixed $args,...) Groups the query by a SQL function.
  108. * @method DataMapper having_func() having_func(string $function_name, mixed $args,..., string $value) Groups the querying using a HAVING clause.
  109. * @method DataMapper or_having_func() having_func(string $function_name, mixed $args,..., string $value) Groups the querying using a HAVING clause, via OR.
  110. * @method DataMapper order_by_func() order_by_func(string $function_name, mixed $args,..., string $direction) Orders the query based on a SQL function.
  111. *
  112. * Field -> SQL functions:
  113. * @method DataMapper where_field_field_func() where_field_func($field, string $function_name, mixed $args,...) Limits results based on a SQL function.
  114. * @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.
  115. * @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.
  116. * @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.
  117. * @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.
  118. * @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.
  119. * @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.
  120. * @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.
  121. * @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.
  122. * @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.
  123. * @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).
  124. * @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).
  125. * @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).
  126. * @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).
  127. * @method DataMapper group_by_field_field_func() group_by_field_func($field, string $function_name, mixed $args,...) Groups the query by a SQL function.
  128. * @method DataMapper having_field_field_func() having_field_func($field, string $function_name, mixed $args,...) Groups the querying using a HAVING clause.
  129. * @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.
  130. * @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.
  131. *
  132. * Subqueries:
  133. * @method DataMapper select_subquery() select_subquery(DataMapper $subquery, string $alias) Selects the result of a function. Alias is required.
  134. * @method DataMapper where_subquery() where_subquery(mixed $subquery_or_field, mixed $value_or_subquery) Limits results based on a subquery.
  135. * @method DataMapper or_where_subquery() or_where_subquery(mixed $subquery_or_field, mixed $value_or_subquery) Limits results based on a subquery, via OR.
  136. * @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.
  137. * @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.
  138. * @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.
  139. * @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.
  140. * @method DataMapper like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
  141. * @method DataMapper or_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
  142. * @method DataMapper not_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
  143. * @method DataMapper or_not_like_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value.
  144. * @method DataMapper ilike_subquery() like_subquery(DataMapper $subquery, string $value, string $match = 'both') Limits results by matching a subquery to a value (case insensitive).
  145. * @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).
  146. * @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).
  147. * @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).
  148. * @method DataMapper having_subquery() having_subquery(string $field, DataMapper $subquery) Groups the querying using a HAVING clause.
  149. * @method DataMapper or_having_subquery() having_subquery(string $field, DataMapper $subquery) Groups the querying using a HAVING clause, via OR.
  150. * @method DataMapper order_by_subquery() order_by_subquery(DataMapper $subquery, string $direction) Orders the query based on a subquery.
  151. *
  152. * Related Subqueries:
  153. * @method DataMapper where_related_subquery() where_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Limits results based on a subquery.
  154. * @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.
  155. * @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.
  156. * @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.
  157. * @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.
  158. * @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.
  159. * @method DataMapper having_related_subquery() having_related_subquery(mixed $related_model, string $related_field, DataMapper $subquery) Groups the querying using a HAVING clause.
  160. * @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.
  161. *
  162. * Array Extension:
  163. * @method array to_array() to_array($fields = '') NEEDS ARRAY EXTENSION. Converts this object into an associative array. @link DMZ_Array::to_array
  164. * @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
  165. * @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
  166. *
  167. * CSV Extension
  168. * @method bool csv_export() csv_export($filename, $fields = '', $include_header = TRUE) NEEDS CSV EXTENSION. Exports this object as a CSV file.
  169. * @method array csv_import() csv_import($filename, $fields = '', $header_row = TRUE, $callback = NULL) NEEDS CSV EXTENSION. Imports a CSV file into this object.
  170. *
  171. * JSON Extension:
  172. * @method string to_json() to_json($fields = '', $pretty_print = FALSE) NEEDS JSON EXTENSION. Converts this object into a JSON string.
  173. * @method string all_to_json() all_to_json($fields = '', $pretty_print = FALSE) NEEDS JSON EXTENSION. Converts the all array into a JSON string.
  174. * @method bool from_json() from_json($json, $fields = '') NEEDS JSON EXTENSION. Imports the values from a JSON string into this object.
  175. * @method void set_json_content_type() set_json_content_type() NEEDS JSON EXTENSION. Sets the content type header to Content-Type: application/json.
  176. *
  177. * SimpleCache Extension:
  178. * @method DataMapper get_cached() get_cached($limit = '', $offset = '') NEEDS SIMPLECACHE EXTENSION. Enables cacheable queries.
  179. * @method DataMapper clear_cache() get_cached($segment,...) NEEDS SIMPLECACHE EXTENSION. Clears a cache for the specfied segment.
  180. *
  181. * Translate Extension:
  182. *
  183. * Nestedsets Extension:
  184. *
  185. */
  186. class DataMapper implements IteratorAggregate {
  187. /**
  188. * Stores the shared configuration
  189. * @var array
  190. */
  191. static $config = array();
  192. /**
  193. * Stores settings that are common across a specific Model
  194. * @var array
  195. */
  196. static $common = array(DMZ_CLASSNAMES_KEY => array());
  197. /**
  198. * Stores global extensions
  199. * @var array
  200. */
  201. static $global_extensions = array();
  202. /**
  203. * Used to override unset default properties.
  204. * @var array
  205. */
  206. static $_dmz_config_defaults = array(
  207. 'timestamp_format' => 'Y-m-d H:i:s O',
  208. 'created_field' => 'created',
  209. 'updated_field' => 'updated',
  210. 'extensions_path' => 'datamapper',
  211. 'field_label_lang_format' => '${model}_${field}',
  212. );
  213. /**
  214. * Contains any errors that occur during validation, saving, or other
  215. * database access.
  216. * @var DM_Error_Object
  217. */
  218. public $error;
  219. /**
  220. * Used to keep track of the original values from the database, to
  221. * prevent unecessarily changing fields.
  222. * @var object
  223. */
  224. public $stored;
  225. /**
  226. * DB Table Prefix
  227. * @var string
  228. */
  229. public $prefix = '';
  230. /**
  231. * DB Join Table Prefix
  232. * @var string
  233. */
  234. public $join_prefix = '';
  235. /**
  236. * The name of the table for this model (may be automatically generated
  237. * from the classname).
  238. * @var string
  239. */
  240. public $table = '';
  241. /**
  242. * The singular name for this model (may be automatically generated from
  243. * the classname).
  244. * @var string
  245. */
  246. public $model = '';
  247. /**
  248. * Can be used to override the default database behavior.
  249. * @var mixed
  250. */
  251. public $db_params = '';
  252. /**
  253. * Prefix string used when reporting errors.
  254. * @var string
  255. */
  256. public $error_prefix = '';
  257. /**
  258. * Suffic string used when reporting errors.
  259. * @var string
  260. */
  261. public $error_suffix = '';
  262. /**
  263. * Custom name for the automatic timestamp saved with new objects.
  264. * Defaults to 'created'.
  265. * @var string
  266. */
  267. public $created_field = '';
  268. /**
  269. * Custom name for the automatic timestamp saved when an object changes.
  270. * Defaults to 'updated'.
  271. * @var string
  272. */
  273. public $updated_field = '';
  274. /**
  275. * If TRUE, automatically wrap every save and delete in a transaction.
  276. * @var bool
  277. */
  278. public $auto_transaction = FALSE;
  279. /**
  280. * If TRUE, has_many relationships are automatically loaded when accessed.
  281. * Not recommended in most situations.
  282. * @var bool
  283. */
  284. public $auto_populate_has_many = FALSE;
  285. /**
  286. * If TRUE, has_one relationships are automatically loaded when accessed.
  287. * Not recommended in some situations.
  288. * @var bool
  289. */
  290. public $auto_populate_has_one = FALSE;
  291. /**
  292. * Enables the old method of storing the all array using an object's ID.
  293. * @var bool
  294. */
  295. public $all_array_uses_ids = FALSE;
  296. /**
  297. * The result of validate is stored here.
  298. * @var bool
  299. */
  300. public $valid = FALSE;
  301. /**
  302. * If TRUE, the created/updated fields are stored using local time.
  303. * If FALSE (the default), they are stored using UTC
  304. * @var bool
  305. */
  306. public $local_time = FALSE;
  307. /**
  308. * If TRUE, the created/updated fields are stored as a unix timestamp,
  309. * as opposed to a formatted string.
  310. * Defaults to FALSE.
  311. * @var bool
  312. */
  313. public $unix_timestamp = FALSE;
  314. /**
  315. * Set to a date format to override the default format of
  316. * 'Y-m-d H:i:s O'
  317. * @var string
  318. */
  319. public $timestamp_format = '';
  320. /**
  321. * Contains the database fields for this object.
  322. * ** Automatically configured **
  323. * @var array
  324. */
  325. public $fields = array();
  326. /**
  327. * Set to a string to use when autoloading lang files.
  328. * Can contain two magic values: ${model} and ${table}.
  329. * These are automatically
  330. * replaced when looking up the language file.
  331. * Defaults to model_${model}
  332. * @var string
  333. */
  334. public $lang_file_format = '';
  335. /**
  336. * Set to a string to use when looking up field labels. Can contain three
  337. * magic values: ${model}, ${table}, and ${field}. These are automatically
  338. * replaced when looking up the language file.
  339. * Defaults to ${model}_${field}
  340. * @var string
  341. */
  342. public $field_label_lang_format = '';
  343. /**
  344. * Contains the result of the last query.
  345. * @var array
  346. */
  347. public $all = array();
  348. /**
  349. * Semi-private field used to track the parent model/id if there is one.
  350. * @var array
  351. */
  352. public $parent = array();
  353. /**
  354. * Contains the validation rules, label, and get_rules for each field.
  355. * @var array
  356. */
  357. public $validation = array();
  358. /**
  359. * Contains any related objects of which this model is related one or more times.
  360. * @var array
  361. */
  362. public $has_many = array();
  363. /**
  364. * Contains any related objects of which this model is singularly related.
  365. * @var array
  366. */
  367. public $has_one = array();
  368. /**
  369. * Used to enable or disable the production cache.
  370. * This should really only be set in the global configuration.
  371. * @var bool
  372. */
  373. public $production_cache = FALSE;
  374. /**
  375. * Used to determine where to look for extensions.
  376. * This should really only be set in the global configuration.
  377. * @var string
  378. */
  379. public $extensions_path = '';
  380. /**
  381. * If set to an array of names, this will automatically load the
  382. * specified extensions for this model.
  383. * @var mixed
  384. */
  385. public $extensions = NULL;
  386. /**
  387. * If a query returns more than the number of rows specified here,
  388. * then it will be automatically freed after a get.
  389. * @var int
  390. */
  391. public $free_result_threshold = 100;
  392. /**
  393. * This can be specified as an array of fields to sort by if no other
  394. * sorting or selection has occurred.
  395. * @var mixed
  396. */
  397. public $default_order_by = NULL;
  398. // tracks whether or not the object has already been validated
  399. protected $_validated = FALSE;
  400. // Tracks the columns that need to be instantiated after a GET
  401. protected $_instantiations = NULL;
  402. // Tracks get_rules, matches, and intval rules, to spped up _to_object
  403. protected $_field_tracking = NULL;
  404. // used to track related queries in deep relationships.
  405. protected $_query_related = array();
  406. // If true before a related get(), any extra fields on the join table will be added.
  407. protected $_include_join_fields = FALSE;
  408. // If true before a save, this will force the next save to be new.
  409. protected $_force_save_as_new = FALSE;
  410. // If true, the next where statement will not be prefixed with an AND or OR.
  411. protected $_where_group_started = FALSE;
  412. /**
  413. * Constructors (both PHP4 and PHP5 style, to stay compatible)
  414. *
  415. * Initialize DataMapper.
  416. * @param int $id if provided, load in the object specified by that ID.
  417. */
  418. public function __construct($id = NULL)
  419. {
  420. return $this->DataMapper($id);
  421. }
  422. public function DataMapper($id = NULL)
  423. {
  424. $this->_dmz_assign_libraries();
  425. $this_class = strtolower(get_class($this));
  426. $is_dmz = $this_class == 'datamapper';
  427. if($is_dmz)
  428. {
  429. $this->_load_languages();
  430. $this->_load_helpers();
  431. }
  432. // this is to ensure that singular is only called once per model
  433. if(isset(DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class])) {
  434. $common_key = DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class];
  435. } else {
  436. DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class] = $common_key = singular($this_class);
  437. }
  438. // Determine model name
  439. if (empty($this->model))
  440. {
  441. $this->model = $common_key;
  442. }
  443. // Load stored config settings by reference
  444. foreach (DataMapper::$config as $config_key => &$config_value)
  445. {
  446. // Only if they're not already set
  447. if (empty($this->{$config_key}))
  448. {
  449. $this->{$config_key} =& $config_value;
  450. }
  451. }
  452. // Load model settings if not in common storage
  453. if ( ! isset(DataMapper::$common[$common_key]))
  454. {
  455. // If model is 'datamapper' then this is the initial autoload by CodeIgniter
  456. if ($is_dmz)
  457. {
  458. // Load config settings
  459. $this->config->load('datamapper', TRUE, TRUE);
  460. // Get and store config settings
  461. DataMapper::$config = $this->config->item('datamapper');
  462. // now double check that all required config values were set
  463. foreach(DataMapper::$_dmz_config_defaults as $config_key => $config_value)
  464. {
  465. if(empty(DataMapper::$config[$config_key]))
  466. {
  467. DataMapper::$config[$config_key] = $config_value;
  468. }
  469. }
  470. DataMapper::_load_extensions(DataMapper::$global_extensions, DataMapper::$config['extensions']);
  471. unset(DataMapper::$config['extensions']);
  472. return;
  473. }
  474. // load language file, if requested and it exists
  475. if(!empty($this->lang_file_format))
  476. {
  477. $lang_file = str_replace(array('${model}', '${table}'), array($this->model, $this->table), $this->lang_file_format);
  478. $deft_lang = $this->config->item('language');
  479. $idiom = ($deft_lang == '') ? 'english' : $deft_lang;
  480. if(file_exists(APPPATH.'language/'.$idiom.'/'.$lang_file.'_lang'.EXT))
  481. {
  482. $this->lang->load($lang_file, $idiom);
  483. }
  484. }
  485. $loaded_from_cache = FALSE;
  486. // Load in the production cache for this model, if it exists
  487. if( ! empty(DataMapper::$config['production_cache']))
  488. {
  489. // check if it's a fully qualified path first
  490. if (!is_dir($cache_folder = DataMapper::$config['production_cache']))
  491. {
  492. // if not, it's relative to the application path
  493. $cache_folder = APPPATH . DataMapper::$config['production_cache'];
  494. }
  495. if(file_exists($cache_folder) && is_dir($cache_folder) && is_writeable($cache_folder))
  496. {
  497. $cache_file = $cache_folder . '/' . $common_key . EXT;
  498. if(file_exists($cache_file))
  499. {
  500. include($cache_file);
  501. if(isset($cache))
  502. {
  503. DataMapper::$common[$common_key] =& $cache;
  504. unset($cache);
  505. // allow subclasses to add initializations
  506. if(method_exists($this, 'post_model_init'))
  507. {
  508. $this->post_model_init(TRUE);
  509. }
  510. // Load extensions (they are not cacheable)
  511. $this->_initiate_local_extensions($common_key);
  512. $loaded_from_cache = TRUE;
  513. }
  514. }
  515. }
  516. }
  517. if(! $loaded_from_cache)
  518. {
  519. // Determine table name
  520. if (empty($this->table))
  521. {
  522. $this->table = plural(get_class($this));
  523. }
  524. // Add prefix to table
  525. $this->table = $this->prefix . $this->table;
  526. $this->_field_tracking = array(
  527. 'get_rules' => array(),
  528. 'matches' => array(),
  529. 'intval' => array('id')
  530. );
  531. // Convert validation into associative array by field name
  532. $associative_validation = array();
  533. foreach ($this->validation as $name => $validation)
  534. {
  535. if(is_string($name)) {
  536. $validation['field'] = $name;
  537. } else {
  538. $name = $validation['field'];
  539. }
  540. // clean up possibly missing fields
  541. if( ! isset($validation['rules']))
  542. {
  543. $validation['rules'] = array();
  544. }
  545. // Populate associative validation array
  546. $associative_validation[$name] = $validation;
  547. if (!empty($validation['get_rules']))
  548. {
  549. $this->_field_tracking['get_rules'][] = $name;
  550. }
  551. // Check if there is a "matches" validation rule
  552. if (isset($validation['rules']['matches']))
  553. {
  554. $this->_field_tracking['matches'][$name] = $validation['rules']['matches'];
  555. }
  556. }
  557. // set up id column, if not set
  558. if(!isset($associative_validation['id']))
  559. {
  560. // label is set below, to prevent caching language-based labels
  561. $associative_validation['id'] = array(
  562. 'field' => 'id',
  563. 'rules' => array('integer')
  564. );
  565. }
  566. $this->validation = $associative_validation;
  567. // Force all other has_one ITFKs to integers on get
  568. foreach($this->has_one as $related => $rel_props)
  569. {
  570. $field = $related . '_id';
  571. if( in_array($field, $this->fields) &&
  572. ( ! isset($this->validation[$field]) || // does not have a validation key or...
  573. ! isset($this->validation[$field]['get_rules'])) && // a get_rules key...
  574. ( ! isset($this->validation[$related]) || // nor does the related have a validation key or...
  575. ! isset($this->validation[$related]['get_rules'])) ) // a get_rules key
  576. {
  577. // assume an int
  578. $this->_field_tracking['intval'][] = $field;
  579. }
  580. }
  581. // Get and store the table's field names and meta data
  582. $fields = $this->db->field_data($this->table);
  583. // Store only the field names and ensure validation list includes all fields
  584. foreach ($fields as $field)
  585. {
  586. // Populate fields array
  587. $this->fields[] = $field->name;
  588. // Add validation if current field has none
  589. if ( ! isset($this->validation[$field->name]))
  590. {
  591. // label is set below, to prevent caching language-based labels
  592. $this->validation[$field->name] = array('field' => $field->name, 'rules' => array());
  593. }
  594. }
  595. // convert simple has_one and has_many arrays into more advanced ones
  596. foreach(array('has_one', 'has_many') as $arr)
  597. {
  598. foreach ($this->{$arr} as $related_field => $rel_props)
  599. {
  600. // process the relationship
  601. $this->_relationship($arr, $rel_props, $related_field);
  602. }
  603. }
  604. // allow subclasses to add initializations
  605. if(method_exists($this, 'post_model_init'))
  606. {
  607. $this->post_model_init(FALSE);
  608. }
  609. // Store common model settings
  610. foreach (array('table', 'fields', 'validation',
  611. 'has_one', 'has_many', '_field_tracking') as $item)
  612. {
  613. DataMapper::$common[$common_key][$item] = $this->{$item};
  614. }
  615. // store the item to the production cache
  616. $this->production_cache();
  617. // Load extensions last, so they aren't cached.
  618. $this->_initiate_local_extensions($common_key);
  619. }
  620. // Finally, localize the labels here (because they shouldn't be cached
  621. // This also sets any missing labels.
  622. $validation =& DataMapper::$common[$common_key]['validation'];
  623. foreach($validation as $field => &$val)
  624. {
  625. // Localize label if necessary
  626. $val['label'] = $this->localize_label($field,
  627. isset($val['label']) ?
  628. $val['label'] :
  629. FALSE);
  630. }
  631. unset($validation);
  632. }
  633. // Load stored common model settings by reference
  634. foreach(DataMapper::$common[$common_key] as $key => &$value)
  635. {
  636. $this->{$key} =& $value;
  637. }
  638. // Clear object properties to set at default values
  639. $this->clear();
  640. if( ! empty($id) && is_numeric($id))
  641. {
  642. $this->get_by_id(intval($id));
  643. }
  644. }
  645. // --------------------------------------------------------------------
  646. /**
  647. * Reloads in the configuration data for a model. This is mainly
  648. * used to handle language changes. Only this instance and new instances
  649. * will see the changes.
  650. */
  651. public function reinitialize_model()
  652. {
  653. // this is to ensure that singular is only called once per model
  654. if(isset(DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class])) {
  655. $common_key = DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class];
  656. } else {
  657. DataMapper::$common[DMZ_CLASSNAMES_KEY][$this_class] = $common_key = singular($this_class);
  658. }
  659. unset(DataMapper::$common[$common_key]);
  660. $model = get_class($this);
  661. new $model(); // re-initialze
  662. // Load stored common model settings by reference
  663. foreach(DataMapper::$common[$common_key] as $key => &$value)
  664. {
  665. $this->{$key} =& $value;
  666. }
  667. }
  668. // --------------------------------------------------------------------
  669. /**
  670. * Autoload
  671. *
  672. * Autoloads object classes that are used with DataMapper.
  673. * This method will look in any model directories available to CI.
  674. *
  675. * Note:
  676. * It is important that they are autoloaded as loading them manually with
  677. * CodeIgniter's loader class will cause DataMapper's __get and __set functions
  678. * to not function.
  679. *
  680. * @param string $class Name of class to load.
  681. */
  682. public static function autoload($class)
  683. {
  684. $CI =& get_instance();
  685. // Don't attempt to autoload CI_ , EE_, or custom prefixed classes
  686. if (in_array(substr($class, 0, 3), array('CI_', 'EE_')) OR strpos($class, $CI->config->item('subclass_prefix')) === 0)
  687. {
  688. return;
  689. }
  690. // Prepare class
  691. $class = strtolower($class);
  692. // Prepare path
  693. if (isset($CI->load->_ci_model_paths) && is_array($CI->load->_ci_model_paths))
  694. {
  695. // use CI 2.0 loader's model paths
  696. $paths = $CI->load->_ci_model_paths;
  697. }
  698. else
  699. {
  700. // search only the applications models folder
  701. $paths[] = APPPATH;
  702. }
  703. foreach ($paths as $path)
  704. {
  705. // Prepare file
  706. $file = $path . 'models/' . $class . EXT;
  707. // Check if file exists, require_once if it does
  708. if (file_exists($file))
  709. {
  710. require_once($file);
  711. break;
  712. }
  713. }
  714. // if class not loaded, do a recursive search of model paths for the class
  715. if (! class_exists($class))
  716. {
  717. foreach($paths as $path)
  718. {
  719. $found = DataMapper::recursive_require_once($class, $path . 'models');
  720. if($found)
  721. {
  722. break;
  723. }
  724. }
  725. }
  726. }
  727. // --------------------------------------------------------------------
  728. /**
  729. * Recursive Require Once
  730. *
  731. * Recursively searches the path for the class, require_once if found.
  732. *
  733. * @param string $class Name of class to look for
  734. * @param string $path Current path to search
  735. */
  736. protected static function recursive_require_once($class, $path)
  737. {
  738. $found = FALSE;
  739. if(is_dir($path))
  740. {
  741. $handle = opendir($path);
  742. if ($handle)
  743. {
  744. while (FALSE !== ($dir = readdir($handle)))
  745. {
  746. // If dir does not contain a dot
  747. if (strpos($dir, '.') === FALSE)
  748. {
  749. // Prepare recursive path
  750. $recursive_path = $path . '/' . $dir;
  751. // Prepare file
  752. $file = $recursive_path . '/' . $class . EXT;
  753. // Check if file exists, require_once if it does
  754. if (file_exists($file))
  755. {
  756. require_once($file);
  757. $found = TRUE;
  758. break;
  759. }
  760. else if (is_dir($recursive_path))
  761. {
  762. // Do a recursive search of the path for the class
  763. DataMapper::recursive_require_once($class, $recursive_path);
  764. }
  765. }
  766. }
  767. closedir($handle);
  768. }
  769. }
  770. return $found;
  771. }
  772. // --------------------------------------------------------------------
  773. /**
  774. * Loads in any extensions used by this class or globally.
  775. *
  776. * @param array $extensions List of extensions to add to.
  777. * @param array $name List of new extensions to load.
  778. */
  779. protected static function _load_extensions(&$extensions, $names)
  780. {
  781. $CI =& get_instance();
  782. $class_prefixes = array(
  783. 0 => 'DMZ_',
  784. 1 => 'DataMapper_',
  785. 2 => $CI->config->item('subclass_prefix'),
  786. 3 => 'CI_'
  787. );
  788. foreach($names as $name => $options)
  789. {
  790. if( ! is_string($name))
  791. {
  792. $name = $options;
  793. $options = NULL;
  794. }
  795. // only load an extension if it wasn't already loaded in this context
  796. if(isset($extensions[$name]))
  797. {
  798. return;
  799. }
  800. if( ! isset($extensions['_methods']))
  801. {
  802. $extensions['_methods'] = array();
  803. }
  804. // determine the file name and class name
  805. if(strpos($name, '/') === FALSE)
  806. {
  807. $file = APPPATH . DataMapper::$config['extensions_path'] . '/' . $name . EXT;
  808. $ext = $name;
  809. }
  810. else
  811. {
  812. $file = APPPATH . $name . EXT;
  813. $ext = array_pop(explode('/', $name));
  814. }
  815. if(!file_exists($file))
  816. {
  817. show_error('DataMapper Error: loading extension ' . $name . ': File not found.');
  818. }
  819. // load class
  820. include_once($file);
  821. // Allow for DMZ_Extension, DataMapper_Extension, etc.
  822. foreach($class_prefixes as $index => $prefix)
  823. {
  824. if(class_exists($prefix.$ext))
  825. {
  826. if($index == 2) // "MY_"
  827. {
  828. // Load in the library this class is based on
  829. $CI->load->library($ext);
  830. }
  831. $ext = $prefix.$ext;
  832. break;
  833. }
  834. }
  835. if(!class_exists($ext))
  836. {
  837. show_error("DataMapper Error: Unable to find a class for extension $name.");
  838. }
  839. // create class
  840. if(is_null($options))
  841. {
  842. $o = new $ext();
  843. }
  844. else
  845. {
  846. $o = new $ext($options);
  847. }
  848. $extensions[$name] = $o;
  849. // figure out which methods can be called on this class.
  850. $methods = get_class_methods($ext);
  851. foreach($methods as $m)
  852. {
  853. // do not load private methods or methods already loaded.
  854. if($m[0] !== '_' &&
  855. is_callable(array($o, $m)) &&
  856. ! isset($extensions['_methods'][$m])
  857. ) {
  858. // store this method.
  859. $extensions['_methods'][$m] = $name;
  860. }
  861. }
  862. }
  863. }
  864. // --------------------------------------------------------------------
  865. /**
  866. * Loads the extensions that are local to this model.
  867. * @param string $common_key Shared key to save extenions to.
  868. */
  869. private function _initiate_local_extensions($common_key)
  870. {
  871. if(!empty($this->extensions))
  872. {
  873. $extensions = $this->extensions;
  874. $this->extensions = array();
  875. DataMapper::_load_extensions($this->extensions, $extensions);
  876. }
  877. else
  878. {
  879. // ensure an empty array
  880. $this->extensions = array('_methods' => array());
  881. }
  882. // bind to the shared key, for dynamic loading
  883. DataMapper::$common[$common_key]['extensions'] =& $this->extensions;
  884. }
  885. // --------------------------------------------------------------------
  886. /**
  887. * Dynamically load an extension when needed.
  888. * @param object $name Name of the extension (or array of extensions).
  889. * @param array $options Options for the extension
  890. * @param boolean $local If TRUE, only loads the extension into this object
  891. */
  892. public function load_extension($name, $options = NULL, $local = FALSE)
  893. {
  894. if( ! is_array($name))
  895. {
  896. if( ! is_null($options))
  897. {
  898. $name = array($name => $options);
  899. }
  900. else
  901. {
  902. $name = array($name);
  903. }
  904. }
  905. // called individually to ensure that the array is modified directly
  906. // (and not copied instead)
  907. if($local)
  908. {
  909. DataMapper::_load_extensions($this->extensions, $name);
  910. }
  911. else
  912. {
  913. DataMapper::_load_extensions(DataMapper::$global_extensions, $name);
  914. }
  915. }
  916. // --------------------------------------------------------------------
  917. /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  918. * *
  919. * Magic methods *
  920. * *
  921. * The following are methods to override the default PHP behaviour. *
  922. * *
  923. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
  924. // --------------------------------------------------------------------
  925. /**
  926. * Magic Get
  927. *
  928. * Returns the value of the named property.
  929. * If named property is a related item, instantiate it first.
  930. *
  931. * This method also instantiates the DB object and the form_validation
  932. * objects as necessary
  933. *
  934. * @ignore
  935. * @param string $name Name of property to look for
  936. * @return mixed
  937. */
  938. public function __get($name)
  939. {
  940. // We dynamically get DB when needed, and create a copy.
  941. // This allows multiple queries to be generated at the same time.
  942. if($name == 'db')
  943. {
  944. $CI =& get_instance();
  945. if($this->db_params === FALSE)
  946. {
  947. $this->db =& $CI->db;
  948. }
  949. else
  950. {
  951. if($this->db_params == '' || $this->db_params === TRUE)
  952. {
  953. // ensure the shared DB is disconnected, even if the app exits uncleanly
  954. if(!isset($CI->db->_has_shutdown_hook))
  955. {
  956. register_shutdown_function(array($CI->db, 'close'));
  957. $CI->db->_has_shutdown_hook = TRUE;
  958. }
  959. // clone, so we don't create additional connections to the DB
  960. $this->db = clone($CI->db);
  961. $this->db->_reset_select();
  962. }
  963. else
  964. {
  965. // connecting to a different database, so we *must* create additional copies.
  966. // It is up to the developer to close the connection!
  967. $this->db = $CI->load->database($this->db_params, TRUE, TRUE);
  968. }
  969. // these items are shared (for debugging)
  970. if(isset($CI->db))
  971. {
  972. $this->db->queries =& $CI->db->queries;
  973. $this->db->query_times =& $CI->db->query_times;
  974. }
  975. }
  976. // ensure the created DB is disconnected, even if the app exits uncleanly
  977. if(!isset($this->db->_has_shutdown_hook))
  978. {
  979. register_shutdown_function(array($this->db, 'close'));
  980. $this->db->_has_shutdown_hook = TRUE;
  981. }
  982. return $this->db;
  983. }
  984. // Special case to get form_validation when first accessed
  985. if($name == 'form_validation')
  986. {
  987. if ( ! isset($this->form_validation) )
  988. {
  989. $CI =& get_instance();
  990. if( ! isset($CI->form_validation))
  991. {
  992. $CI->load->library('form_validation');
  993. $this->lang->load('form_validation');
  994. unset($CI->load->_ci_classes['form_validation']);
  995. }
  996. $this->form_validation = $CI->form_validation;
  997. }
  998. return $this->form_validation;
  999. }
  1000. $has_many = isset($this->has_many[$name]);
  1001. $has_one = isset($this->has_one[$name]);
  1002. // If named property is a "has many" or "has one" related item
  1003. if ($has_many || $has_one)
  1004. {
  1005. $related_properties = $has_many ? $this->has_many[$name] : $this->has_one[$name];
  1006. // Instantiate it before accessing
  1007. $class = $related_properties['class'];
  1008. $this->{$name} = new $class();
  1009. // Store parent data
  1010. $this->{$name}->parent = array('model' => $related_properties['other_field'], 'id' => $this->id);
  1011. // Check if Auto Populate for "has many" or "has one" is on
  1012. // (but only if this object exists in the DB, and we aren't instantiating)
  1013. if ($this->exists() &&
  1014. ($has_many && $this->auto_populate_has_many) || ($has_one && $this->auto_populate_has_one))
  1015. {
  1016. $this->{$name}->get();
  1017. }
  1018. return $this->{$name};
  1019. }
  1020. $name_single = singular($name);
  1021. if($name_single !== $name) {
  1022. // possibly return single form of name
  1023. $test = $this->{$name_single};
  1024. if(is_object($test)) {
  1025. return $test;
  1026. }
  1027. }
  1028. return NULL;
  1029. }
  1030. // --------------------------------------------------------------------
  1031. /**
  1032. * Used several places to temporarily override the auto_populate setting
  1033. * @ignore
  1034. * @param string $related Related Name
  1035. * @return DataMapper|NULL
  1036. */
  1037. private function &_get_without_auto_populating($related)
  1038. {
  1039. $b_many = $this->auto_populate_has_many;
  1040. $b_one = $this->auto_populate_has_one;
  1041. $this->auto_populate_has_many = FALSE;
  1042. $this->auto_populate_has_one = FALSE;
  1043. $ret =& $this->{$related};
  1044. $this->auto_populate_has_many = $b_many;
  1045. $this->auto_populate_has_one = $b_one;
  1046. return $ret;
  1047. }
  1048. // --------------------------------------------------------------------
  1049. /**
  1050. * Magic Call
  1051. *
  1052. * Calls special methods, or extension methods.
  1053. *
  1054. * @ignore
  1055. * @param string $method Method name
  1056. * @param array $arguments Arguments to method
  1057. * @return mixed
  1058. */
  1059. public function __call($method, $arguments)
  1060. {
  1061. // List of watched method names
  1062. // NOTE: order matters: make sure more specific items are listed before
  1063. // less specific items
  1064. $watched_methods = array(
  1065. 'save_', 'delete_',
  1066. 'get_by_related_', 'get_by_related', 'get_by_',
  1067. '_related_subquery', '_subquery',
  1068. '_related_', '_related',
  1069. '_join_field',
  1070. '_field_func', '_func'
  1071. );
  1072. foreach ($watched_methods as $watched_method)
  1073. {
  1074. // See if called method is a watched method
  1075. if (strpos($method, $watched_method) !== FALSE)
  1076. {
  1077. $pieces = explode($watched_method, $method);
  1078. if ( ! empty($pieces[0]) && ! empty($pieces[1]))
  1079. {
  1080. // Watched method is in the middle
  1081. return $this->{'_' . trim($watched_method, '_')}($pieces[0], array_merge(array($pieces[1]), $arguments));
  1082. }
  1083. else
  1084. {
  1085. // Watched method is a prefix or suffix
  1086. return $this->{'_' . trim($watched_method, '_')}(str_replace($watched_method, '', $method), $arguments);
  1087. }
  1088. }
  1089. }
  1090. // attempt to call an extension
  1091. $ext = NULL;
  1092. if($this->_extension_method_exists($method, 'local'))
  1093. {
  1094. $name = $this->extensions['_methods'][$method];
  1095. $ext = $this->extensions[$name];
  1096. }
  1097. else if($this->_extension_method_exists($method, 'global'))
  1098. {
  1099. $name = DataMapper::$global_extensions['_methods'][$method];
  1100. $ext = DataMapper::$global_extensions[$name];
  1101. }
  1102. if( ! is_null($ext))
  1103. {
  1104. array_unshift($arguments, $this);
  1105. return call_user_func_array(array($ext, $method), $arguments);
  1106. }
  1107. // show an error, for debugging's sake.
  1108. throw new Exception("Unable to call the method \"$method\" on the class " . get_class($this));
  1109. }
  1110. // --------------------------------------------------------------------
  1111. /**
  1112. * Returns TRUE or FALSE if the method exists in the extensions.
  1113. *
  1114. * @param object $method Method to look for.
  1115. * @param object $which One of 'both', 'local', or 'global'
  1116. * @return bool TRUE if the method can be called.
  1117. */
  1118. private function _extension_method_exists($method, $which = 'both') {
  1119. $found = FALSE;
  1120. if($which != 'global') {
  1121. $found = ! empty($this->extensions) && isset($this->extensions['_methods'][$method]);
  1122. }
  1123. if( ! $found && $which != 'local' ) {
  1124. $found = ! empty(DataMapper::$global_extensions) && isset(DataMapper::$global_extensions['_methods'][$method]);
  1125. }
  1126. return $found;
  1127. }
  1128. // --------------------------------------------------------------------
  1129. /**
  1130. * Magic Clone
  1131. *
  1132. * Allows for a less shallow clone than the default PHP clone.
  1133. *
  1134. * @ignore
  1135. */
  1136. public function __clone()
  1137. {
  1138. foreach ($this as $key => $value)
  1139. {
  1140. if (is_object($value) && $key != 'db')
  1141. {
  1142. $this->{$key} = clone($value);
  1143. }
  1144. }
  1145. }
  1146. // --------------------------------------------------------------------
  1147. /**
  1148. * To String
  1149. *
  1150. * Converts the current object into a string.
  1151. * Should be overridden by extended objects.
  1152. *
  1153. * @return string
  1154. */
  1155. public function __toString()
  1156. {
  1157. return ucfirst($this->model);
  1158. }
  1159. // -----------------------------------------------…

Large files files are truncated, but you can click here to view the full file