PageRenderTime 67ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 1ms

/application/libraries/datamapper.php

https://bitbucket.org/alexdeoliveira/ignitercms
PHP | 6756 lines | 3363 code | 828 blank | 2565 comment | 587 complexity | 82bfbef425fc5df97a8f8c842a8e15a6 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0

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

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