PHPonTrax
[ class tree: PHPonTrax ] [ index: PHPonTrax ] [ all elements ]

Source for file active_record.php

Documentation is available at active_record.php

  1. <?php
  2. /**
  3. * File containing the ActiveRecord class
  4. *
  5. * (PHP 5)
  6. *
  7. * @package PHPonTrax
  8. * @version $Id: active_record.php 198 2006-04-20 16:20:30Z haas $
  9. * @copyright (c) 2005 John Peterson
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining
  12. * a copy of this software and associated documentation files (the
  13. * "Software"), to deal in the Software without restriction, including
  14. * without limitation the rights to use, copy, modify, merge, publish,
  15. * distribute, sublicense, and/or sell copies of the Software, and to
  16. * permit persons to whom the Software is furnished to do so, subject to
  17. * the following conditions:
  18. *
  19. * The above copyright notice and this permission notice shall be
  20. * included in all copies or substantial portions of the Software.
  21. *
  22. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  23. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  24. * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  27. * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  28. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29. */
  30.  
  31. /**
  32. * Load the {@link http://pear.php.net/manual/en/package.pear.php PEAR base class}
  33. */
  34. require_once('PEAR.php');
  35.  
  36. /**
  37. * Load the {@link http://pear.php.net/manual/en/package.database.db.php PEAR DB package}
  38. */
  39. require_once('DB.php');
  40.  
  41. /**
  42. * Base class for the ActiveRecord design pattern
  43. *
  44. * <p>Each subclass of this class is associated with a database table
  45. * in the Model section of the Model-View-Controller architecture.
  46. * By convention, the name of each subclass is the CamelCase singular
  47. * form of the table name, which is in the lower_case_underscore
  48. * plural notation. For example,
  49. * a table named "order_details" would be associated with a subclass
  50. * of ActiveRecord named "OrderDetail", and a table named "people"
  51. * would be associated with subclass "Person". See the tutorial
  52. * {@tutorial PHPonTrax/naming.pkg}</p>
  53. *
  54. * <p>For a discussion of the ActiveRecord design pattern, see
  55. * "Patterns of Enterprise
  56. * Application Architecture" by Martin Fowler, pp. 160-164.</p>
  57. *
  58. * <p>Unit tester: {@link ActiveRecordTest}</p>
  59. *
  60. * @tutorial PHPonTrax/ActiveRecord.cls
  61. */
  62. class ActiveRecord {
  63.  
  64. /**
  65. * Reference to the database object
  66. *
  67. * Reference to the database object returned by
  68. * {@link http://pear.php.net/manual/en/package.database.db.db.connect.php PEAR DB::Connect()}
  69. * @var object DB
  70. * see
  71. * {@link http://pear.php.net/manual/en/package.database.db.php PEAR DB}
  72. */
  73. private static $db = null;
  74.  
  75. /**
  76. * Description of a row in the associated table in the database
  77. *
  78. * <p>Retrieved from the RDBMS by {@link set_content_columns()}.
  79. * See {@link }
  80. * http://pear.php.net/manual/en/package.database.db.db-common.tableinfo.php
  81. * DB_common::tableInfo()} for the format. <b>NOTE:</b> Some
  82. * RDBMS's don't return all values.</p>
  83. *
  84. * <p>An additional element 'human_name' is added to each column
  85. * by {@link set_content_columns()}. The actual value contained
  86. * in each column is stored in an object variable with the name
  87. * given by the 'name' element of the column description for each
  88. * column.</p>
  89. *
  90. * <p><b>NOTE:</b>The information from the database about which
  91. * columns are primary keys is <b>not used</b>. Instead, the
  92. * primary keys in the table are listed in {@link $primary_keys},
  93. * which is maintained independently.</p>
  94. * @var string[]
  95. * @see $primary_keys
  96. * @see quoted_attributes()
  97. * @see __set()
  98. */
  99. public $content_columns = null; # info about each column in the table
  100.  
  101. /**
  102. * Table name
  103. *
  104. * Name of the table in the database associated with the subclass.
  105. * Normally set to the pluralized lower case underscore form of
  106. * the class name by the constructor. May be overridden.
  107. * @var string
  108. */
  109. public $table_name = null;
  110.  
  111. /**
  112. * Database name override
  113. *
  114. * Name of the database to use, if you are not using the value
  115. * read from file config/database.ini
  116. * @var string
  117. */
  118. public $database_name = null;
  119.  
  120. /**
  121. * Mode to use when fetching data from database
  122. *
  123. * See {@link }
  124. * http://pear.php.net/manual/en/package.database.db.db-common.setfetchmode.php
  125. * the relevant PEAR DB class documentation}
  126. * @var integer
  127. */
  128. public $fetch_mode = DB_FETCHMODE_ASSOC;
  129.  
  130. /**
  131. * Force reconnect to database
  132. *
  133. * @var boolean
  134. */
  135. public $force_reconnect = false; # should we force a connection everytime
  136. public $index_on = "id"; # find_all returns an array of objects each object index is off of this field
  137.  
  138. # Table associations
  139. /**
  140. * @todo Document this variable
  141. * @var string[]
  142. */
  143. protected $has_many = null;
  144.  
  145. /**
  146. * @todo Document this variable
  147. * @var string[]
  148. */
  149. protected $has_one = null;
  150.  
  151. /**
  152. * @todo Document this variable
  153. * @var string[]
  154. */
  155. protected $has_and_belongs_to_many = null;
  156.  
  157. /**
  158. * @todo Document this variable
  159. * @var string[]
  160. */
  161. protected $belongs_to = null;
  162.  
  163. /**
  164. * @todo Document this variable
  165. * @var string[]
  166. */
  167. protected $habtm_attributes = null;
  168.  
  169. /**
  170. * @todo Document this property
  171. */
  172. protected $save_associations = array();
  173. /**
  174. * @todo Document this property
  175. * @var boolean
  176. */
  177. public $auto_save_associations = true; # where or not to auto save defined associations if set
  178.  
  179. /**
  180. * Whether this object represents a new record
  181. *
  182. * true => This object was created without reading a row from the
  183. * database, so use SQL 'INSERT' to put it in the database.
  184. * false => This object was a row read from the database, so use
  185. * SQL 'UPDATE' to update database with new values.
  186. * @var boolean
  187. */
  188. protected $new_record = true;
  189.  
  190. /**
  191. * Names of automatic update timestamp columns
  192. *
  193. * When a row containing one of these columns is updated and
  194. * {@link $auto_timestamps} is true, update the contents of the
  195. * timestamp columns with the current date and time.
  196. * @see $auto_timestamps
  197. * @see $auto_create_timestamps
  198. * @var string[]
  199. */
  200. protected $auto_update_timestamps = array("updated_at","updated_on");
  201.  
  202. /**
  203. * Names of automatic create timestamp columns
  204. *
  205. * When a row containing one of these columns is created and
  206. * {@link $auto_timestamps} is true, store the current date and
  207. * time in the timestamp columns.
  208. * @see $auto_timestamps
  209. * @see $auto_update_timestamps
  210. * @var string[]
  211. */
  212. protected $auto_create_timestamps = array("created_at","created_on");
  213.  
  214. /**
  215. * Date format for use with auto timestamping
  216. *
  217. * The format for this should be compatiable with the php date() function.
  218. * http://www.php.net/date
  219. * @var string
  220. */
  221. protected $date_format = "Y-m-d";
  222.  
  223. /**
  224. * Time format for use with auto timestamping
  225. *
  226. * The format for this should be compatiable with the php date() function.
  227. * http://www.php.net/date
  228. * @var string
  229. */
  230. protected $time_format = "H:i:s";
  231. /**
  232. * Whether to keep date/datetime fields NULL if not set
  233. *
  234. * true => If date field is not set it try to preserve NULL
  235. * false => Don't try to preserve NULL if field is already NULL
  236. * @var boolean
  237. */
  238. protected $preserve_null_dates = true;
  239.  
  240. /**
  241. * SQL aggregate functions that may be applied to the associated
  242. * table.
  243. *
  244. * SQL defines aggregate functions AVG, COUNT, MAX, MIN and SUM.
  245. * Not all of these functions are implemented by all DBMS's
  246. * @var string[]
  247. */
  248. protected $aggregrations = array("count","sum","avg","max","min");
  249. /**
  250. * Primary key of the associated table
  251. *
  252. * Array element(s) name the primary key column(s), as used to
  253. * specify the row to be updated or deleted. To be a primary key
  254. * a column must be listed both here and in {@link }
  255. * $content_columns}. <b>NOTE:</b>This
  256. * field is maintained by hand. It is not derived from the table
  257. * description read from the database.
  258. * @var string[]
  259. * @see $content_columns
  260. * @see find()
  261. * @see find_all()
  262. * @see find_first()
  263. */
  264. public $primary_keys = array("id");
  265.  
  266. /**
  267. * Default for how many rows to return from {@link find_all()}
  268. * @var integer
  269. */
  270. public $rows_per_page_default = 20;
  271.  
  272. /**
  273. * @todo Document this variable
  274. */
  275. public $display = 10; # Pagination how many numbers in the list << < 1 2 3 4 > >>
  276.  
  277. /**
  278. * Description of non-fatal errors found
  279. *
  280. * For every non-fatal error found, an element describing the
  281. * error is added to $errors. Initialized to an empty array in
  282. * {@link valid()} before validating object. When an error
  283. * message is associated with a particular attribute, the message
  284. * should be stored with the attribute name as its key. If the
  285. * message is independent of attributes, store it with a numeric
  286. * key beginning with 0.
  287. *
  288. * @var string[]
  289. * @see add_error()
  290. * @see get_errors()
  291. */
  292. public $errors = array();
  293.  
  294. /**
  295. * Whether to automatically update timestamps in certain columns
  296. *
  297. * @see $auto_create_timestamps
  298. * @see $auto_update_timestamps
  299. * @var boolean
  300. */
  301. public $auto_timestamps = true;
  302.  
  303. /**
  304. * @todo Document this variable
  305. */
  306. public $auto_save_habtm = true; # auto insert / update $has_and_belongs_to_many tables
  307.  
  308. /**
  309. * @todo Document this variable
  310. */
  311. public $auto_delete_habtm = true; # auto delete $has_and_belongs_to_many associations
  312.  
  313. /**
  314. * Transactions (only use if your db supports it)
  315. * <b>FIXME: static should be after private</b>
  316. */
  317. static private $begin_executed = false; # this is for transactions only to let query() know that a 'BEGIN' has been executed
  318.  
  319. /**
  320. * <b>FIXME: static should be after public</b>
  321. */
  322. static public $use_transactions = false; # this will issue a rollback command if any sql fails
  323.  
  324. /**
  325. * Construct an ActiveRecord object
  326. *
  327. * <ol>
  328. * <li>Establish a connection to the database</li>
  329. * <li>Find the name of the table associated with this object</li>
  330. * <li>Read description of this table from the database</li>
  331. * <li>Optionally apply update information to column attributes</li>
  332. * </ol>
  333. * @param string[] $attributes Updates to column attributes
  334. * @uses establish_connection()
  335. * @uses set_content_columns()
  336. * @uses $table_name
  337. * @uses set_table_name_using_class_name()
  338. * @uses update_attributes()
  339. */
  340. function __construct($attributes = null) {
  341. # Open the database connection
  342. $this->establish_connection();
  343.  
  344. # Set $table_name
  345. if($this->table_name == null) {
  346. $this->set_table_name_using_class_name();
  347. }
  348.  
  349. # Set column info
  350. if($this->table_name) {
  351. $this->set_content_columns($this->table_name);
  352. }
  353.  
  354. # If $attributes array is passed in update the class with its contents
  355. if(is_array($attributes)) {
  356. $this->update_attributes($attributes);
  357. }
  358. }
  359.  
  360. /**
  361. * Override get() if they do $model->some_association->field_name
  362. * dynamically load the requested contents from the database.
  363. * @todo Document this API
  364. * @uses $belongs_to
  365. * @uses get_association_type()
  366. * @uses $has_and_belongs_to_many
  367. * @uses $has_many
  368. * @uses $has_one
  369. * @uses find_all_has_many()
  370. * @uses find_all_habtm()
  371. * @uses find_one_belongs_to()
  372. * @uses find_one_has_one()
  373. */
  374. function __get($key) {
  375. $association_type = $this->get_association_type($key);
  376. if (is_null($association_type)) {
  377. return null;
  378. }
  379. switch($association_type) {
  380. case "has_many":
  381. $parameters = is_array($this->has_many) ? $this->has_many[$key] : null;
  382. $this->$key = $this->find_all_has_many($key, $parameters);
  383. break;
  384. case "has_one":
  385. $parameters = is_array($this->has_one) ? $this->has_one[$key] : null;
  386. $this->$key = $this->find_one_has_one($key, $parameters);
  387. break;
  388. case "belongs_to":
  389. $parameters = is_array($this->belongs_to) ? $this->belongs_to[$key] : null;
  390. $this->$key = $this->find_one_belongs_to($key, $parameters);
  391. break;
  392. case "has_and_belongs_to_many":
  393. $parameters = is_array($this->has_and_belongs_to_many) ? $this->has_and_belongs_to_many[$key] : null;
  394. $this->$key = $this->find_all_habtm($key, $parameters);
  395. break;
  396. }
  397. //echo "<pre>id: $this->id<br>getting: $key = ".$this->$key."<br></pre>";
  398. return $this->$key;
  399. }
  400.  
  401. /**
  402. * Store column value or description of the table format
  403. *
  404. * If called with key 'table_name', $value is stored as the
  405. * description of the table format in $content_columns.
  406. * Any other key causes an object variable with the same name to
  407. * be created and stored into. If the value of $key matches the
  408. * name of a column in content_columns, the corresponding object
  409. * variable becomes the content of the column in this row.
  410. * @uses $auto_save_associations
  411. * @uses get_association_type()
  412. * @uses set_content_columns()
  413. */
  414. function __set($key, $value) {
  415. //echo "setting: $key = $value<br>";
  416. if($key == "table_name") {
  417. $this->set_content_columns($value);
  418. # this elseif checks if first its an object if its parent is ActiveRecord
  419. } elseif(is_object($value) && get_parent_class($value) == __CLASS__ && $this->auto_save_associations) {
  420. if($association_type = $this->get_association_type($key)) {
  421. $this->save_associations[$association_type][] = $value;
  422. if($association_type == "belongs_to") {
  423. $foreign_key = Inflector::singularize($value->table_name)."_id";
  424. $this->$foreign_key = $value->id;
  425. }
  426. }
  427. # this elseif checks if its an array of objects and if its parent is ActiveRecord
  428. } elseif(is_array($value) && $this->auto_save_associations) {
  429. if($association_type = $this->get_association_type($key)) {
  430. $this->save_associations[$association_type][] = $value;
  431. }
  432. }
  433. // Assignment to something else, do it
  434. $this->$key = $value;
  435. }
  436.  
  437. /**
  438. * Override call() to dynamically call the database associations
  439. * @todo Document this API
  440. * @uses $aggregrations
  441. * @uses aggregrate_all()
  442. * @uses get_association_type()
  443. * @uses $belongs_to
  444. * @uses $has_one
  445. * @uses $has_and_belongs_to_many
  446. * @uses $has_many
  447. * @uses find_all_by()
  448. * @uses find_by()
  449. */
  450. function __call($method_name, $parameters) {
  451. if(method_exists($this,$method_name)) {
  452. # If the method exists, just call it
  453. $result = call_user_func(array($this,$method_name), $parameters);
  454. } else {
  455. # ... otherwise, check to see if the method call is one of our
  456. # special Trax methods ...
  457. # ... first check for method names that match any of our explicitly
  458. # declared associations for this model ( e.g. $this->has_many = array("movies" => null) ) ...
  459. $association_type = $this->get_association_type($method_name);
  460. switch($association_type) {
  461. case "has_many":
  462. $result = $this->find_all_has_many($method_name, $parameters);
  463. break;
  464. case "has_one":
  465. $result = $this->find_one_has_one($method_name, $parameters);
  466. break;
  467. case "belongs_to":
  468. $result = $this->find_one_belongs_to($method_name, $parameters);
  469. break;
  470. case "has_and_belongs_to_many":
  471. $result = $this->find_all_habtm($method_name, $parameters);
  472. break;
  473. }
  474.  
  475. # check for the [count,sum,avg,etc...]_all magic functions
  476. if(substr($method_name, -4) == "_all" && in_array(substr($method_name, 0, -4),$this->aggregrations)) {
  477. //echo "calling method: $method_name<br>";
  478. $result = $this->aggregrate_all($method_name, $parameters);
  479. }
  480. # check for the find_all_by_* magic functions
  481. elseif(strlen($method_name) > 11 && substr($method_name, 0, 11) == "find_all_by") {
  482. //echo "calling method: $method_name<br>";
  483. $result = $this->find_by($method_name, $parameters, true);
  484. }
  485. # check for the find_by_* magic functions
  486. elseif(strlen($method_name) > 7 && substr($method_name, 0, 7) == "find_by") {
  487. //echo "calling method: $method_name<br>";
  488. $result = $this->find_by($method_name, $parameters);
  489. }
  490. }
  491. return $result;
  492. }
  493.  
  494. /**
  495. * Returns a the name of the join table that would be used for the two
  496. * tables. The join table name is decided from the alphabetical order
  497. * of the two tables. e.g. "genres_movies" because "g" comes before "m"
  498. *
  499. * Parameters: $first_table, $second_table: the names of two database tables,
  500. * e.g. "movies" and "genres"
  501. * @todo Document this API
  502. */
  503. private function get_join_table_name($first_table, $second_table) {
  504. $tables = array();
  505. $tables["one"] = $first_table;
  506. $tables["many"] = $second_table;
  507. @asort($tables);
  508. return @implode("_", $tables);
  509. }
  510.  
  511. /**
  512. * Find all records using a "has_and_belongs_to_many" relationship
  513. * (many-to-many with a join table in between). Note that you can also
  514. * specify an optional "paging limit" by setting the corresponding "limit"
  515. * instance variable. For example, if you want to return 10 movies from the
  516. * 5th movie on, you could set $this->movies_limit = "10, 5"
  517. *
  518. * Parameters: $this_table_name: The name of the database table that has the
  519. * one row you are interested in. E.g. genres
  520. * $other_table_name: The name of the database table that has the
  521. * many rows you are interested in. E.g. movies
  522. * Returns: An array of ActiveRecord objects. (e.g. Movie objects)
  523. * @todo Document this API
  524. */
  525. private function find_all_habtm($other_table_name, $parameters = null) {
  526. $other_class_name = Inflector::classify($other_table_name);
  527. # Instantiate an object to access find_all
  528. $results = new $other_class_name();
  529.  
  530. # Prepare the join table name primary keys (fields) to do the join on
  531. $join_table = $this->get_join_table_name($this->table_name, $other_table_name);
  532. $this_foreign_key = Inflector::singularize($this->table_name)."_id";
  533. $other_foreign_key = Inflector::singularize($other_table_name)."_id";
  534. # Set up the SQL segments
  535. $conditions = "{$join_table}.{$this_foreign_key}=".intval($this->id);
  536. $orderings = null;
  537. $limit = null;
  538. $joins = "LEFT JOIN {$join_table} ON {$other_table_name}.id = {$other_foreign_key}";
  539.  
  540. # Use any passed-in parameters
  541. if (!is_null($parameters)) {
  542. if(@array_key_exists("conditions", $parameters))
  543. $additional_conditions = $parameters['conditions'];
  544. elseif($parameters[0] != "")
  545. $additional_conditions = $parameters[0];
  546.  
  547. if(@array_key_exists("orderings", $parameters))
  548. $orderings = $parameters['orderings'];
  549. elseif($parameters[1] != "")
  550. $orderings = $parameters[1];
  551.  
  552. if(@array_key_exists("limit", $parameters))
  553. $limit = $parameters['limit'];
  554. elseif($parameters[2] != "")
  555. $limit = $parameters[2];
  556.  
  557. if(@array_key_exists("joins", $parameters))
  558. $additional_joins = $parameters['joins'];
  559. elseif($parameters[3] != "")
  560. $additional_joins = $parameters[3];
  561.  
  562. if (!empty($additional_conditions))
  563. $conditions .= " AND (" . $additional_conditions . ")";
  564. if (!empty($additional_joins))
  565. $joins .= " " . $additional_joins;
  566. }
  567.  
  568. # Get the list of other_class_name objects
  569. return $results->find_all($conditions, $orderings, $limit, $joins);
  570. }
  571.  
  572. /**
  573. * Find all records using a "has_many" relationship (one-to-many)
  574. *
  575. * Parameters: $other_table_name: The name of the other table that contains
  576. * many rows relating to this object's id.
  577. * Returns: An array of ActiveRecord objects. (e.g. Contact objects)
  578. * @todo Document this API
  579. */
  580. private function find_all_has_many($other_table_name, $parameters = null) {
  581. # Prepare the class name and primary key, e.g. if
  582. # customers has_many contacts, then we'll need a Contact
  583. # object, and the customer_id field name.
  584. if(@array_key_exists("foreign_key", $parameters))
  585. $foreign_key = $parameters['foreign_key'];
  586. else
  587. $foreign_key = Inflector::singularize($this->table_name)."_id";
  588.  
  589. $other_class_name = Inflector::classify($other_table_name);
  590. $conditions = "{$foreign_key}=$this->id";
  591.  
  592. # Use any passed-in parameters
  593. if (!is_null($parameters)) {
  594. //echo "<pre>";print_r($parameters);
  595. if(@array_key_exists("conditions", $parameters))
  596. $additional_conditions = $parameters['conditions'];
  597. elseif($parameters[0] != "")
  598. $additional_conditions = $parameters[0];
  599.  
  600. if(@array_key_exists("orderings", $parameters))
  601. $orderings = $parameters['orderings'];
  602. elseif($parameters[1] != "")
  603. $orderings = $parameters[1];
  604.  
  605. if(@array_key_exists("limit", $parameters))
  606. $limit = $parameters['limit'];
  607. elseif($parameters[2] != "")
  608. $limit = $parameters[2];
  609.  
  610. if(@array_key_exists("joins", $parameters))
  611. $additional_joins = $parameters['joins'];
  612. elseif($parameters[3] != "")
  613. $additional_joins = $parameters[3];
  614.  
  615. if(!empty($additional_conditions))
  616. $conditions .= " AND (" . $additional_conditions . ")";
  617. if(!empty($additional_joins))
  618. $joins .= " " . $additional_joins;
  619. }
  620.  
  621. # Instantiate an object to access find_all
  622. $other_class_object = new $other_class_name();
  623. # Get the list of other_class_name objects
  624. $results = $other_class_object->find_all($conditions, $orderings, $limit, $joins);
  625.  
  626. return $results;
  627. }
  628.  
  629. /**
  630. * Find all records using a "has_one" relationship (one-to-one)
  631. * (the foreign key being in the other table)
  632. * Parameters: $other_table_name: The name of the other table that contains
  633. * many rows relating to this object's id.
  634. * Returns: An array of ActiveRecord objects. (e.g. Contact objects)
  635. * @todo Document this API
  636. */
  637. private function find_one_has_one($other_object_name, $parameters = null) {
  638. # Prepare the class name and primary key, e.g. if
  639. # customers has_many contacts, then we'll need a Contact
  640. # object, and the customer_id field name.
  641. $other_class_name = Inflector::camelize($other_object_name);
  642. if(@array_key_exists("foreign_key", $parameters))
  643. $foreign_key = $parameters['foreign_key'];
  644. else
  645. $foreign_key = Inflector::singularize($this->table_name)."_id";
  646.  
  647. $conditions = "$foreign_key='{$this->id}'";
  648. # Instantiate an object to access find_all
  649. $results = new $other_class_name();
  650. # Get the list of other_class_name objects
  651. $results = $results->find_first($conditions, $orderings);
  652. # There should only be one result, an object, if so return it
  653. if(is_object($results)) {
  654. return $results;
  655. } else {
  656. return null;
  657. }
  658. }
  659.  
  660. /**
  661. * Find all records using a "belongs_to" relationship (one-to-one)
  662. * (the foreign key being in the table itself)
  663. * Parameters: $other_object_name: The singularized version of a table name.
  664. * E.g. If the Contact class belongs_to the
  665. * Customer class, then $other_object_name
  666. * will be "customer".
  667. * @todo Document this API
  668. */
  669. private function find_one_belongs_to($other_object_name, $parameters = null) {
  670. # Prepare the class name and primary key, e.g. if
  671. # customers has_many contacts, then we'll need a Contact
  672. # object, and the customer_id field name.
  673. $other_class_name = Inflector::camelize($other_object_name);
  674. if(@array_key_exists("foreign_key", $parameters))
  675. $foreign_key = $parameters['foreign_key'];
  676. else
  677. $foreign_key = $other_object_name."_id";
  678.  
  679. $conditions = "id='".$this->$foreign_key."'";
  680. # Instantiate an object to access find_all
  681. $results = new $other_class_name();
  682. # Get the list of other_class_name objects
  683. $results = $results->find_first($conditions, $orderings);
  684. # There should only be one result, an object, if so return it
  685. if(is_object($results)) {
  686. return $results;
  687. } else {
  688. return null;
  689. }
  690. }
  691.  
  692. /**
  693. * Implement *_all() functions (SQL aggregate functions)
  694. *
  695. * Apply one of the SQL aggregate functions to a column of the
  696. * table associated with this object. The SQL aggregate
  697. * functions are AVG, COUNT, MAX, MIN and SUM. Not all DBMS's
  698. * implement all of these functions.
  699. * @param string $agrregrate_type SQL aggregate function to
  700. * apply, suffixed '_all'. The aggregate function is one of
  701. * the strings in {@link $aggregrations}.
  702. * @param string[] $parameters Conditions to apply to the
  703. * aggregate function. If present, must be an array of three
  704. * strings:<ol>
  705. * <li>$parameters[0]: If present, expression to apply
  706. * the aggregate function to. Otherwise, '*' will be used.
  707. * <b>NOTE:</b>SQL uses '*' only for the COUNT() function,
  708. * where it means "including rows with NULL in this column".</li>
  709. * <li>$parameters[1]: argument to WHERE clause</li>
  710. * <li>$parameters[2]: joins??? @todo Document this parameter</li>
  711. * </ol>
  712. * @throws {@link ActiveRecordError}
  713. * @uses query()
  714. * @uses is_error()
  715. */
  716. private function aggregrate_all($aggregrate_type, $parameters = null) {
  717. $aggregrate_type = strtoupper(substr($aggregrate_type, 0, -4));
  718. ($parameters[0]) ? $field = $parameters[0] : $field = "*";
  719. $sql = "SELECT $aggregrate_type($field) AS agg_result FROM $this->table_name ";
  720. # Use any passed-in parameters
  721. if (!is_null($parameters)) {
  722. $conditions = $parameters[1];
  723. $joins = $parameters[2];
  724. }
  725.  
  726. if(!empty($joins)) $sql .= ",$joins ";
  727. if(!empty($conditions)) $sql .= "WHERE $conditions ";
  728.  
  729. //echo "sql:$sql<br>";
  730. if($this->is_error($rs = $this->query($sql))) {
  731. $this->raise($rs->getMessage());
  732. } else {
  733. $row = $rs->fetchRow();
  734. return $row["agg_result"];
  735. }
  736. return 0;
  737. }
  738.  
  739. /**
  740. * Test whether this object represents a new record
  741. * @uses $new_record
  742. * @return boolean Whether this object represents a new record
  743. */
  744. function is_new_record() {
  745. return $this->new_record;
  746. }
  747.  
  748. /**
  749. * get the attributes for a specific column.
  750. * @uses $content_columns
  751. * @todo Document this API
  752. */
  753. function column_for_attribute($attribute) {
  754. if(is_array($this->content_columns)) {
  755. foreach($this->content_columns as $column) {
  756. if($column['name'] == $attribute) {
  757. return $column;
  758. }
  759. }
  760. }
  761. return null;
  762. }
  763. /**
  764. * Check whether a column exists in the associated table
  765. *
  766. * When called, {@link $content_columns} lists the columns in
  767. * the table described by this object.
  768. * @param string Name of the column
  769. * @return boolean true=>the column exists; false=>it doesn't
  770. * @uses content_columns
  771. */
  772. function column_attribute_exists($attribute) {
  773. if(is_array($this->content_columns)) {
  774. foreach($this->content_columns as $column) {
  775. if($column['name'] == $attribute) {
  776. return true;
  777. }
  778. }
  779. }
  780. return false;
  781. }
  782.  
  783. /**
  784. * Get contents of one column of record selected by id and table
  785. *
  786. * When called, {@link $id} identifies one record in the table
  787. * identified by {@link $table}. Fetch from the database the
  788. * contents of column $column of this record.
  789. * @param string Name of column to retrieve
  790. * @uses $db
  791. * @uses column_attribute_exists()
  792. * @throws {@link ActiveRecordError}
  793. * @uses is_error()
  794. */
  795. function send($column) {
  796. if($this->column_attribute_exists($column)) {
  797. # Run the query to grab a specific columns value.
  798. $sql = "SELECT $column FROM $this->table_name WHERE id='$this->id'";
  799. $this->log_query($sql);
  800. $result = self::$db->getOne($sql);
  801. if($this->is_error($result)) {
  802. $this->raise($result->getMessage());
  803. }
  804. }
  805. return $result;
  806. }
  807.  
  808. /**
  809. * Only used if you want to do transactions and your db supports transactions
  810. *
  811. * @uses $db
  812. * @todo Document this API
  813. */
  814. function begin() {
  815. self::$db->query("BEGIN");
  816. $this->begin_executed = true;
  817. }
  818.  
  819. /**
  820. * Only used if you want to do transactions and your db supports transactions
  821. *
  822. * @uses $db
  823. * @todo Document this API
  824. */
  825. function commit() {
  826. self::$db->query("COMMIT");
  827. $this->begin_executed = false;
  828. }
  829.  
  830. /**
  831. * Only used if you want to do transactions and your db supports transactions
  832. *
  833. * @uses $db
  834. * @todo Document this API
  835. */
  836. function rollback() {
  837. self::$db->query("ROLLBACK");
  838. }
  839.  
  840. /**
  841. * Perform an SQL query and return the results
  842. *
  843. * @param string $sql SQL for the query command
  844. * @return DB_result {@link http://pear.php.net/manual/en/package.database.db.db-result.php}
  845. * Result set from query
  846. * @uses $db
  847. * @uses is_error()
  848. * @uses log_query()
  849. * @throws {@link ActiveRecordError}
  850. */
  851. function query($sql) {
  852. # Run the query
  853. $this->log_query($sql);
  854. $rs = self::$db->query($sql);
  855. if ($this->is_error($rs)) {
  856. if(self::$use_transactions && self::$begin_executed) {
  857. $this->rollback();
  858. }
  859. $this->raise($rs->getMessage());
  860. }
  861. return $rs;
  862. }
  863.  
  864. /**
  865. * Implement find_by_*() and find_all_by_* methods
  866. *
  867. * Converts a method name beginning 'find_by_' or 'find_all_by_'
  868. * into a query for rows matching the rest of the method name and
  869. * the arguments to the function. The part of the method name
  870. * after '_by' is parsed for columns and logical relationships
  871. * (AND and OR) to match. For example, the call
  872. * find_by_fname('Ben')
  873. * is converted to
  874. * SELECT * ... WHERE fname='Ben'
  875. * and the call
  876. * find_by_fname_and_lname('Ben','Dover')
  877. * is converted to
  878. * SELECT * ... WHERE fname='Ben' AND lname='Dover'
  879. *
  880. * @uses find_all()
  881. * @uses find_first()
  882. */
  883. private function find_by($method_name, $parameters, $find_all = false) {
  884. $method_parts = explode("_",substr($method_name, ($find_all ? 12 : 8)));
  885. if(is_array($method_parts)) {
  886. $param_cnt = 0;
  887. $part_cnt = 1;
  888. $and_cnt = substr_count(strtolower($method_name), "_and_");
  889. $or_cnt = substr_count(strtolower($method_name), "_or_");
  890. $part_size = count($method_parts) - $and_cnt - $or_cnt;
  891. // FIXME: This loop doesn't work right for either
  892. // find_by_first_name_and_last_name or
  893. // find_all_by_first_name_and_last_name
  894. foreach($method_parts as $part) {
  895. if(strtoupper($part) == "AND") {
  896. $method_params .= implode("_",$field)."='".$parameters[$param_cnt++]."' AND ";
  897. $part_cnt--;
  898. unset($field);
  899. } elseif(strtoupper($part) == "OR") {
  900. $method_params .= implode("_",$field)."='".$parameters[$param_cnt++]."' OR ";
  901. $part_cnt--;
  902. unset($field);
  903. } else {
  904. $field[] = $part;
  905. if($part_size == $part_cnt) {
  906. $method_params .= implode("_",$field)."='".$parameters[$param_cnt++]."'";
  907. if($parameters[$param_cnt]) {
  908. $orderings = $parameters[$param_cnt];
  909. }
  910. }
  911. }
  912. $part_cnt++;
  913. }
  914.  
  915. if($find_all) {
  916. return $this->find_all($method_params, $orderings);
  917. } else {
  918. return $this->find_first($method_params, $orderings);
  919. }
  920. }
  921. }
  922.  
  923. /**
  924. * Return rows selected by $conditions
  925. *
  926. * If no rows match, an empty array is returned.
  927. * @param string SQL to use in the query. If
  928. * $conditions contains "SELECT", then $orderings, $limit and
  929. * $joins are ignored and the query is completely specified by
  930. * $conditions. If $conditions is omitted or does not contain
  931. * "SELECT", "SELECT * FROM" will be used. If $conditions is
  932. * specified and does not contain "SELECT", the query will
  933. * include "WHERE $conditions". If $conditions is null, the
  934. * entire table is returned.
  935. * @param string Argument to "ORDER BY" in query.
  936. * If specified, the query will include
  937. * "ORDER BY $orderings". If omitted, no ordering will be
  938. * applied.
  939. * @param integer[] Page, rows per page???
  940. * @param string ???
  941. * @todo Document the $limit and $joins parameters
  942. * @uses $rows_per_page_default
  943. * @uses $rows_per_page
  944. * @uses $offset
  945. * @uses $page
  946. * @uses is_error()
  947. * @uses $new_record
  948. * @uses query()
  949. * @return object[] Array of objects of the same class as this
  950. * object, one object for each row returned by the query.
  951. * If the column 'id' was in the results, it is used as the key
  952. * for that object in the array.
  953. * @throws {@link ActiveRecordError}
  954. */
  955. function find_all($conditions = null, $orderings = null,
  956. $limit = null, $joins = null) {
  957. //error_log("find_all(".(is_null($conditions)?'null':$conditions)
  958. // .', ' . (is_null($orderings)?'null':$orderings)
  959. // .', ' . (is_null($limit)?'null':var_export($limit,true))
  960. // .', ' . (is_null($joins)?'null':$joins).')');
  961. // Is output to be generated in pages?
  962. if (is_array($limit)) {
  963.  
  964. // Yes, get next page number and rows per page from argument
  965. list($this->page, $this->rows_per_page) = $limit;
  966. if($this->page <= 0) {
  967. $this->page = 1;
  968. }
  969. # Default for rows_per_page:
  970. if ($this->rows_per_page == null) {
  971. $this->rows_per_page = $this->rows_per_page_default;
  972. }
  973. # Set the LIMIT string segment for the SQL in the find_all
  974. $this->offset = ($this->page - 1) * $this->rows_per_page;
  975. # mysql 3.23 doesn't support OFFSET
  976. //$limit = "$rows_per_page OFFSET $offset";
  977. $limit = $this->offset.", ".$this->rows_per_page;
  978.  
  979. // Remember that we're generating output in pages
  980. // so $limit needs to be added to the query
  981. $set_pages = true;
  982. }
  983.  
  984. // Test source of SQL for query
  985. if(stristr($conditions, "SELECT")) {
  986.  
  987. // SQL completely specified in argument so use it as is
  988. $sql = $conditions;
  989. } else {
  990.  
  991. // SQL will be built from specifications in argument
  992. $sql = "SELECT * FROM ".$this->table_name." ";
  993.  
  994. // If join specified, include it
  995. if(!is_null($joins)) {
  996. if(substr($joins,0,4) != "LEFT") $sql .= ",";
  997. $sql .= " $joins ";
  998. }
  999.  
  1000. // If conditions specified, include them
  1001. if(!is_null($conditions)) {
  1002. $sql .= "WHERE $conditions ";
  1003. }
  1004.  
  1005. // If ordering specified, include it
  1006. if(!is_null($orderings)) {
  1007. $sql .= "ORDER BY $orderings ";
  1008. }
  1009.  
  1010. // If limit specified, divide into pages
  1011. if(!is_null($limit)) {
  1012. // FIXME: Isn't the second test redundant?
  1013. if($set_pages) {
  1014.  
  1015. // Send query to database
  1016. //echo "query: $sql\n";
  1017. if($this->is_error($rs = $this->query($sql))) {
  1018.  
  1019. // Error returned, throw error exception
  1020. $this->raise($rs->getMessage());
  1021. // Execution doesn't return here
  1022. } else {
  1023.  
  1024. # Set number of total pages in result set
  1025. # without the LIMIT
  1026. if($count = $rs->numRows())
  1027. $this->pages = (
  1028. ($count % $this->rows_per_page) == 0)
  1029. ? $count / $this->
  1030. rows_per_page
  1031. : floor($count / $this->rows_per_page) + 1;
  1032. }
  1033. }
  1034. $sql .= "LIMIT $limit";
  1035. }
  1036. }
  1037.  
  1038. //echo "ActiveRecord::find_all() - sql: $sql\n<br>";
  1039. //echo "query: $sql\n";
  1040. if($this->is_error($rs = $this->query($sql))) {
  1041. $this->raise($rs->getMessage());
  1042. }
  1043.  
  1044. $objects = array();
  1045. while($row = $rs->fetchRow()) {
  1046. #$class = get_class($this);
  1047. $class = Inflector::classify($this->table_name);
  1048. $object = new $class();
  1049. $object->new_record = false;
  1050. foreach($row as $field => $val) {
  1051. $object->$field = $val;
  1052. if($field == $this->index_on) {
  1053. $objects_key = $val;
  1054. }
  1055. }
  1056. $objects[$objects_key] = $object;
  1057. unset($object);
  1058. unset($objects_key);
  1059. }
  1060. return $objects;
  1061. }
  1062.  
  1063. /**
  1064. * Find row(s) with specified value(s)
  1065. *
  1066. * Find all the rows in the table which match the argument $id.
  1067. * Return zero or more objects of the same class as this
  1068. * class representing the rows that matched the argument.
  1069. * @param mixed[] $id If $id is an array then a query will be
  1070. * generated selecting all of the array values in column "id".
  1071. * If $id is a string containing "=" then the string value of
  1072. * $id will be inserted in a WHERE clause in the query. If $id
  1073. * is a scalar not containing "=" then a query will be generated
  1074. * selecting the first row WHERE id = '$id'.
  1075. * <b>NOTE</b> The column name "id" is used regardless of the
  1076. * value of {@link $primary_keys}. Therefore if you need to
  1077. * select based on some column other than "id", you must pass a
  1078. * string argument ready to insert in the SQL SELECT.
  1079. * @param string $orderings Argument to "ORDER BY" in query.
  1080. * If specified, the query will include "ORDER BY
  1081. * $orderings". If omitted, no ordering will be applied.
  1082. * @param integer[] $limit Page, rows per page???
  1083. * @param string $joins ???
  1084. * @todo Document the $limit and $joins parameters
  1085. * @uses find_all()
  1086. * @uses find_first()
  1087. * @return mixed Results of query. If $id was a scalar then the
  1088. * result is an object of the same class as this class and
  1089. * matching $id conditions, or if no row matched the result is
  1090. * null.
  1091. *
  1092. * If $id was an array then the result is an array containing
  1093. * objects of the same class as this class and matching the
  1094. * conditions set by $id. If no rows matched, the array is
  1095. * empty.
  1096. * @throws {@link ActiveRecordError}
  1097. */
  1098. function find($id, $orderings = null, $limit = null, $joins = null) {
  1099. if(is_array($id)) {
  1100. $conditions = "id IN(".implode(",",$id).")";
  1101. } elseif(stristr($id,"=")) { # has an = so must be a where clause
  1102. $conditions = $id;
  1103. } else {
  1104. $conditions = "id='$id'";
  1105. }
  1106.  
  1107. if(is_array($id)) {
  1108. return $this->find_all($conditions, $orderings, $limit, $joins);
  1109. } else {
  1110. return $this->find_first($conditions, $orderings, $limit, $joins);
  1111. }
  1112. }
  1113.  
  1114. /**
  1115. * Return first row selected by $conditions
  1116. *
  1117. * If no rows match, null is returned.
  1118. * @param string $conditions SQL to use in the query. If
  1119. * $conditions contains "SELECT", then $orderings, $limit and
  1120. * $joins are ignored and the query is completely specified by
  1121. * $conditions. If $conditions is omitted or does not contain
  1122. * "SELECT", "SELECT * FROM" will be used. If $conditions is
  1123. * specified and does not contain "SELECT", the query will
  1124. * include "WHERE $conditions". If $conditions is null, the
  1125. * entire table is returned.
  1126. * @param string $orderings Argument to "ORDER BY" in query.
  1127. * If specified, the query will include
  1128. * "ORDER BY $orderings". If omitted, no ordering will be
  1129. * applied.
  1130. * FIXME This parameter doesn't seem to make sense
  1131. * @param integer[] $limit Page, rows per page??? @todo Document this parameter
  1132. * FIXME This parameter doesn't seem to make sense
  1133. * @param string $joins ??? @todo Document this parameter
  1134. * @uses find_all()
  1135. * @return mixed An object of the same class as this class and
  1136. * matching $conditions, or null if none did.
  1137. * @throws {@link ActiveRecordError}
  1138. */
  1139. function find_first($conditions, $orderings = null, $limit = null, $joins = null) {
  1140. $result = $this->find_all($conditions, $orderings, $limit, $joins);
  1141. return @current($result);
  1142. }
  1143.  
  1144. /**
  1145. * Return all the rows selected by the SQL argument
  1146. *
  1147. * If no rows match, an empty array is returned.
  1148. * @param string $sql SQL to use in the query.
  1149. */
  1150. function find_by_sql($sql) {
  1151. return $this->find_all($sql);
  1152. }
  1153.  
  1154. /**
  1155. * Reloads the attributes of this object from the database.
  1156. * @uses get_primary_key_conditions()
  1157. * @todo Document this API
  1158. */
  1159. function reload($conditions = null) {
  1160. if(is_null($conditions)) {
  1161. $conditions = $this->get_primary_key_conditions();
  1162. }
  1163. $object = $this->find($conditions);
  1164. if(is_object($object)) {
  1165. foreach($object as $key => $value) {
  1166. $this->$key = $value;
  1167. }
  1168. return true;
  1169. }
  1170. return false;
  1171. }
  1172.  
  1173. /**
  1174. * Loads into current object values from the database.
  1175. */
  1176. function load($conditions = null) {
  1177. return $this->reload($conditions);
  1178. }
  1179.  
  1180. /**
  1181. * @todo Document this API. What's going on here? It appears to
  1182. * either create a row with all empty values, or it tries
  1183. * to recurse once for each attribute in $attributes.
  1184. * FIXME: resolve calling sequence
  1185. * Creates an object, instantly saves it as a record (if the validation permits it).
  1186. * If the save fails under validations it returns false and $errors array gets set.
  1187. */
  1188. function create($attributes, $dont_validate = false) {
  1189. if(is_array($attributes)) {
  1190. foreach($attributes as $attr) {
  1191. $this->create($attr, $dont_validate);
  1192. }
  1193. } else {
  1194. $class = get_class($this);
  1195. $object = new $class();
  1196. $object->save($attributes, $dont_validate);
  1197. }
  1198. }
  1199.  
  1200. /**
  1201. * Finds the record from the passed id, instantly saves it with the passed attributes
  1202. * (if the validation permits it). Returns true on success and false on error.
  1203. * @todo Document this API
  1204. */
  1205. function update($id, $attributes, $dont_validate = false) {
  1206. if(is_array($id)) {
  1207. foreach($id as $update_id) {
  1208. $this->update($update_id, $attributes[$update_id], $dont_validate);
  1209. }
  1210. } else {
  1211. $object = $this->find($id);
  1212. return $object->save($attributes, $dont_validate);
  1213. }
  1214. }
  1215.  
  1216. /**
  1217. * Updates all records with the SET-part of an SQL update statement in updates and
  1218. * returns an integer with the number of rows updates. A subset of the records can
  1219. * be selected by specifying conditions.
  1220. * Example:
  1221. * $model->update_all("category = 'cooldude', approved = 1", "author = 'John'");
  1222. * @uses is_error()
  1223. * @uses query()
  1224. * @throws {@link ActiveRecordError}
  1225. * @todo Document this API
  1226. */
  1227. function update_all($updates, $conditions = null) {
  1228. $sql = "UPDATE $this->table_name SET $updates WHERE $conditions";
  1229. $result = $this->query($sql);
  1230. if ($this->is_error($result)) {
  1231. $this->raise($result->getMessage());
  1232. } else {
  1233. return true;
  1234. }
  1235. }
  1236.  
  1237. /**
  1238. * Save without valdiating anything.
  1239. * @todo Document this API
  1240. */
  1241. function save_without_validation($attributes = null) {
  1242. return $this->save($attributes, true);
  1243. }
  1244.  
  1245. /**
  1246. * Create or update a row in the table with specified attributes
  1247. *
  1248. * @param string[] $attributes List of name => value pairs giving
  1249. * name and value of attributes to set.
  1250. * @param boolean $dont_validate true => Don't call validation
  1251. * routines before saving the row. If false or omitted, all
  1252. * applicable validation routines are called.
  1253. * @uses add_record_or_update_record()
  1254. * @uses update_attributes()
  1255. * @uses valid()
  1256. * @return boolean
  1257. * <ul>
  1258. * <li>true => row was updated or inserted successfully</li>
  1259. * <li>false => insert failed</li>
  1260. * </ul>
  1261. */
  1262. function save($attributes = null, $dont_validate = false) {
  1263. //error_log("ActiveRecord::save() \$attributes="
  1264. // . var_export($attributes,true));
  1265. if(!is_null($attributes)) {
  1266. $this->update_attributes($attributes);
  1267. }
  1268. if ($dont_validate || $this->valid()) {
  1269. return $this->add_record_or_update_record();
  1270. } else {
  1271. return false;
  1272. }
  1273. }
  1274.  
  1275. /**
  1276. * Create or update a row in the table
  1277. *
  1278. * If this object represents a new row in the table, insert it.
  1279. * Otherwise, update the exiting row. before_?() and after_?()
  1280. * routines will be called depending on whether the row is new.
  1281. * @uses add_record()
  1282. * @uses after_create()
  1283. * @uses after_update()
  1284. * @uses before_create()
  1285. * @uses before_save()
  1286. * @uses $new_record
  1287. * @uses update_record()
  1288. * @return boolean
  1289. * <ul>
  1290. * <li>true => row was updated or inserted successfully</li>
  1291. * <li>false => insert failed</li>
  1292. * </ul>
  1293. */
  1294. private function add_record_or_update_record() {
  1295. //error_log('add_record_or_update_record()');
  1296. $this->before_save();
  1297. if($this->new_record) {
  1298. $this->before_create();
  1299. $result = $this->add_record();
  1300. $this->after_create();
  1301. } else {
  1302. $this->before_update();
  1303. $result = $this->update_record();
  1304. $this->after_update();
  1305. }
  1306. $this->after_save();
  1307. return $result;
  1308. }
  1309.  
  1310. /**
  1311. * Insert a new row in the table associated with this object
  1312. *
  1313. * Build an SQL INSERT statement getting the table name from
  1314. * {@link $table_name}, the column names from {@link
  1315. * $content_columns} and the values from object variables.
  1316. * Send the insert to the RDBMS.
  1317. * FIXME: Shouldn't we be saving the insert ID value as an object
  1318. * variable $this->id?
  1319. * @uses $auto_save_habtm
  1320. * @uses add_habtm_records()
  1321. * @uses before_create()
  1322. * @uses get_insert_id()
  1323. * @uses is_error()
  1324. * @uses query()
  1325. * @uses quoted_attributes()
  1326. * @uses raise()
  1327. * @uses $table_name
  1328. * @return boolean
  1329. * <ul>
  1330. * <li>true => row was inserted successfully</li>
  1331. * <li>false => insert failed</li>
  1332. * </ul>
  1333. * @throws {@link ActiveRecordError}
  1334. */
  1335. private function add_record() {
  1336. $attributes = $this->quoted_attributes();
  1337. $fields = @implode(', ', array_keys($attributes));
  1338. $values = @implode(', ', array_values($attributes));
  1339. $sql = "INSERT INTO $this->table_name ($fields) VALUES ($values)";
  1340. //echo "add_record: SQL: $sql<br>";
  1341. $result = $this->query($sql);
  1342. if ($this->is_error($result)) {
  1343. $this->raise($results->getMessage());
  1344. } else {
  1345. $this->id = $this->get_insert_id();
  1346. if($this->id > 0) {
  1347. if($this->auto_save_habtm) {
  1348. $habtm_result = $this->add_habtm_records($this->id);
  1349. }
  1350. $this->save_associations();
  1351. }
  1352. return ($result && $habtm_result);
  1353. }
  1354. }
  1355.  
  1356. /**
  1357. * Update the row in the table described by this object
  1358. *
  1359. * The primary key attributes must exist and have appropriate
  1360. * non-null values. If a column is listed in {@link
  1361. * $content_columns} but no attribute of that name exists, the
  1362. * column will be set to the null string ''.
  1363. * @todo Describe habtm automatic update
  1364. * @uses is_error()
  1365. * @uses get_updates_sql()
  1366. * @uses get_primary_key_conditions()
  1367. * @uses query()
  1368. * @uses raise()
  1369. * @uses update_habtm_records()
  1370. * @return boolean
  1371. * <ul>
  1372. * <li>true => row was updated successfully</li>
  1373. * <li>false => update failed</li>
  1374. * </ul>
  1375. * @throws {@link ActiveRecordError}
  1376. */
  1377. private function update_record() {
  1378. //error_log('update_record()');
  1379. $updates = $this->get_updates_sql();
  1380. $conditions = $this->get_primary_key_conditions();
  1381. $sql = "UPDATE $this->table_name SET $updates WHERE $conditions";
  1382. //error_log("update_record: SQL: $sql<br>");
  1383. $result = $this->query($sql);
  1384. if($this->is_error($result)) {
  1385. $this->raise($results->getMessage());
  1386. } else {
  1387. if($this->id > 0) {
  1388. if($this->auto_save_habtm) {
  1389. $habtm_result = $this->update_habtm_records($this->id);
  1390. }
  1391. $this->save_associations();
  1392. }
  1393. return ($result && $habtm_result);
  1394. }
  1395. }
  1396. /**
  1397. * returns the association type if defined in child class or null
  1398. * @todo Document this API
  1399. * @todo <b>FIXME:</b> does the match algorithm match a substring
  1400. * of what we want to match?
  1401. * @uses $belongs_to
  1402. * @uses $has_and_belongs_to_many
  1403. * @uses $has_many
  1404. * @uses $has_one
  1405. * @return mixed Association type, one of the following:
  1406. * <ul>
  1407. * <li>"belongs_to"</li>
  1408. * <li>"has_and_belongs_to_many"</li>
  1409. * <li>"has_many"</li>
  1410. * <li>"has_one"</li>
  1411. * </ul>
  1412. * if an association exists, or null if no association
  1413. */
  1414. function get_association_type($association_name) {
  1415. $type = null;
  1416. if(is_string($this->has_many)) {
  1417. if(preg_match("/$association_name/", $this->has_many)) {
  1418. $type = "has_many";
  1419. }
  1420. } elseif(is_array($this->has_many)) {
  1421. if(array_key_exists($association_name, $this->has_many)) {
  1422. $type = "has_many";
  1423. }
  1424. }
  1425. if(is_string($this->has_one)) {
  1426. if(preg_match("/$association_name/", $this->has_one)) {
  1427. $type = "has_one";
  1428. }
  1429. } elseif(is_array($this->has_one)) {
  1430. if(array_key_exists($association_name, $this->has_one)) {
  1431. $type = "has_one";
  1432. }
  1433. }
  1434. if(is_string($this->belongs_to)) {
  1435. if(preg_match("/$association_name/", $this->belongs_to)) {
  1436. $type = "belongs_to";
  1437. }
  1438. } elseif(is_array($this->belongs_to)) {
  1439. if(array_key_exists($association_name, $this->belongs_to)) {
  1440. $type = "belongs_to";
  1441. }
  1442. }
  1443. if(is_string($this->has_and_belongs_to_many)) {
  1444. if(preg_match("/$association_name/", $this->has_and_belongs_to_many)) {
  1445. $type = "has_and_belongs_to_many";
  1446. }
  1447. } elseif(is_array($this->has_and_belongs_to_many)) {
  1448. if(array_key_exists($association_name, $this->has_and_belongs_to_many)) {
  1449. $type = "has_and_belongs_to_many";
  1450. }
  1451. }
  1452. return $type;
  1453. }
  1454. /**
  1455. * Saves any associations objects assigned to this instance
  1456. * @uses $auto_save_associations
  1457. * @todo Document this API
  1458. */
  1459. private function save_associations() {
  1460. if(count($this->save_associations) && $this->auto_save_associations) {
  1461. foreach(array_keys($this->save_associations) as $type) {
  1462. if(count($this->save_associations[$type])) {
  1463. foreach($this->save_associations[$type] as $object_or_array) {
  1464. if(is_object($object_or_array)) {
  1465. $this->save_association($object_or_array, $type);
  1466. } elseif(is_array($object_or_array)) {
  1467. foreach($object_or_array as $object) {
  1468. $this->save_association($object, $type);
  1469. }
  1470. }
  1471. }
  1472. }
  1473. }
  1474. }
  1475. }
  1476. /**
  1477. * save the association to the database
  1478. * @todo Document this API
  1479. */
  1480. private function save_association($object, $type) {
  1481. if(is_object($object) && get_parent_class($object) == __CLASS__ && $type) {
  1482. //echo get_class($object)." - type:$type<br>";
  1483. switch($type) {
  1484. case "has_many":
  1485. case "has_one":
  1486. $foreign_key = Inflector::singularize($this->table_name)."_id";
  1487. $object->$foreign_key = $this->id;
  1488. //echo "fk:$foreign_key = $this->id<br>";
  1489. break;
  1490. }
  1491. $object->save();
  1492. }
  1493. }
  1494.  
  1495. /**
  1496. * Deletes the record with the given $id or if you have done a
  1497. * $model = $model->find($id), then $model->delete() it will delete
  1498. * the record it just loaded from the find() without passing anything
  1499. * to delete(). If an array of ids is provided, all ids in array are deleted.
  1500. * @uses $errors
  1501. * @todo Document this API
  1502. */
  1503. function delete($id = null) {
  1504. if($this->id > 0 && is_null($id)) {
  1505. $id = $this->id;
  1506. }
  1507.  
  1508. if(is_null($id)) {
  1509. $this->errors[] = "No id specified to delete on.";
  1510. return false;
  1511. }
  1512.  
  1513. $this->before_delete();
  1514. $result = $this->delete_all("id IN ($id)");
  1515. if($this->auto_delete_habtm) {
  1516. if(is_string($this->has_and_belongs_to_many)) {
  1517. $habtms = explode(",", $this->has_and_belongs_to_many);
  1518. foreach($habtms as $other_table_name) {
  1519. $this->delete_all_habtm_records(trim($other_table_name), $id);
  1520. }
  1521. } elseif(is_array($this->has_and_belongs_to_many)) {
  1522. foreach($this->has_and_belongs_to_many as $other_table_name => $values) {
  1523. $this->delete_all_habtm_records($other_table_name, $id);
  1524. }
  1525. }
  1526. }
  1527. $this->after_delete();
  1528.  
  1529. return $result;
  1530. }
  1531.  
  1532. /**
  1533. * Delete from table all rows that match argument
  1534. *
  1535. * Delete the row(s), if any, matching the argument.
  1536. * @param string $conditions SQL argument to "WHERE" describing
  1537. * the rows to delete
  1538. * @return boolean
  1539. * <ul>
  1540. * <li>true => One or more rows were deleted</li>
  1541. * <li>false => $conditions was omitted</li>
  1542. * </ul>
  1543. * @uses is_error()
  1544. * @uses $new_record
  1545. * @uses $errors
  1546. * @uses query()
  1547. * @throws {@link ActiveRecordError}
  1548. */
  1549. function delete_all($conditions = null) {
  1550. if(is_null($conditions)) {
  1551. $this->errors[] = "No conditions specified to delete on.";
  1552. return false;
  1553. }
  1554.  
  1555. # Delete the record(s)
  1556. if($this->is_error($rs = $this->query("DELETE FROM $this->table_name WHERE $conditions"))) {
  1557. $this->raise($rs->getMessage());
  1558. }
  1559.  
  1560. // <b>FIXME: We don't know whether this row was deleted.
  1561. // What are the implications of making this a new record?</b>
  1562. $this->id = 0;
  1563. $this->new_record = true;
  1564. return true;
  1565. }
  1566.  
  1567. /**
  1568. * @uses $has_and_belongs_to_many
  1569. * @todo Document this API
  1570. */
  1571. private function set_habtm_attributes($attributes) {
  1572. if(is_array($attributes)) {
  1573. $this->habtm_attributes = array();
  1574. foreach($attributes as $key => $habtm_array) {
  1575. if(is_array($habtm_array)) {
  1576. if(is_string($this->has_and_belongs_to_many)) {
  1577. if(preg_match("/$key/", $this->has_and_belongs_to_many)) {
  1578. $this->habtm_attributes[$key] = $habtm_array;
  1579. }
  1580. } elseif(is_array($this->has_and_belongs_to_many)) {
  1581. if(array_key_exists($key, $this->has_and_belongs_to_many)) {
  1582. $this->habtm_attributes[$key] = $habtm_array;
  1583. }
  1584. }
  1585. }
  1586. }
  1587. }
  1588. }
  1589.  
  1590. /**
  1591. *
  1592. * @todo Document this API
  1593. */
  1594. private function update_habtm_records($this_foreign_value) {
  1595. return $this->add_habtm_records($this_foreign_value);
  1596. }
  1597.  
  1598. /**
  1599. *
  1600. * @uses is_error()
  1601. * @uses query()
  1602. * @throws {@link ActiveRecordError}
  1603. * @todo Document this API
  1604. */
  1605. private function add_habtm_records($this_foreign_value) {
  1606. if($this_foreign_value > 0 && count($this->habtm_attributes) > 0) {
  1607. if($this->delete_habtm_records($this_foreign_value)) {
  1608. reset($this->habtm_attributes);
  1609. foreach($this->habtm_attributes as $other_table_name => $other_foreign_values) {
  1610. $table_name = $this->get_join_table_name($this->table_name,$other_table_name);
  1611. $other_foreign_key = Inflector::singularize($other_table_name)."_id";
  1612. $this_foreign_key = Inflector::singularize($this->table_name)."_id";
  1613. foreach($other_foreign_values as $other_foreign_value) {
  1614. unset($attributes);
  1615. $attributes[$this_foreign_key] = $this_foreign_value;
  1616. $attributes[$other_foreign_key] = $other_foreign_value;
  1617. $attributes = $this->quoted_attributes($attributes);
  1618. $fields = @implode(', ', array_keys($attributes));
  1619. $values = @implode(', ', array_values($attributes));
  1620. $sql = "INSERT INTO $table_name ($fields) VALUES ($values)";
  1621. //echo "add_habtm_records: SQL: $sql<br>";
  1622. $result = $this->query($sql);
  1623. if ($this->is_error($result)) {
  1624. $this->raise($result->getMessage());
  1625. }
  1626. }
  1627. }
  1628. }
  1629. }
  1630. return true;
  1631. }
  1632.  
  1633. /**
  1634. *
  1635. * @uses is_error()
  1636. * @uses query()
  1637. * @throws {@link ActiveRecordError}
  1638. * @todo Document this API
  1639. */
  1640. private function delete_habtm_records($this_foreign_value) {
  1641. if($this_foreign_value > 0 && count($this->habtm_attributes) > 0) {
  1642. reset($this->habtm_attributes);
  1643. foreach($this->habtm_attributes as $other_table_name => $values) {
  1644. $this->delete_all_habtm_records($other_table_name, $this_foreign_value);
  1645. }
  1646. }
  1647. return true;
  1648. }
  1649. private function delete_all_habtm_records($other_table_name, $this_foreign_value) {
  1650. if($other_table_name && $this_foreign_value > 0) {
  1651. $habtm_table_name = $this->get_join_table_name($this->table_name,$other_table_name);
  1652. $this_foreign_key = Inflector::singularize($this->table_name)."_id";
  1653. $sql = "DELETE FROM $habtm_table_name WHERE $this_foreign_key = $this_foreign_value";
  1654. //echo "delete_all_habtm_records: SQL: $sql<br>";
  1655. $result = $this->query($sql);
  1656. if($this->is_error($result)) {
  1657. $this->raise($result->getMessage());
  1658. }
  1659. }
  1660. }
  1661.  
  1662. /**
  1663. * Apply automatic timestamp updates
  1664. *
  1665. * If automatic timestamps are in effect (as indicated by
  1666. * {@link $auto_timestamps} == true) and the column named in the
  1667. * $field argument is of type "timestamp" and matches one of the
  1668. * names in {@link auto_create_timestamps} or {@link
  1669. * auto_update_timestamps}(as selected by {@link $new_record}),
  1670. * then return the current date and time as a string formatted
  1671. * to insert in the database. Otherwise return $value.
  1672. * @uses $new_record
  1673. * @uses $content_columns
  1674. * @uses $auto_timestamps
  1675. * @uses $auto_create_timestamps
  1676. * @uses $auto_update_timestamps
  1677. * @param string $field Name of a column in the table
  1678. * @param mixed $value Value to return if $field is not an
  1679. * automatic timestamp column
  1680. * @return mixed Current date and time or $value
  1681. */
  1682. private function check_datetime($field, $value) {
  1683. if($this->auto_timestamps) {
  1684. if(is_array($this->content_columns)) {
  1685. foreach($this->content_columns as $field_info) {
  1686. if(($field_info['name'] == $field) && stristr($field_info['type'], "date")) {
  1687. $format = ($field_info['type'] == "date") ? $this->date_format : "{$this->date_format} {$this->time_format}";
  1688. if($this->new_record) {
  1689. if(in_array($field, $this->auto_create_timestamps)) {
  1690. return date($format);
  1691. } elseif($this->preserve_null_dates && is_null($value) && !stristr($field_info['flags'], "not_null")) {
  1692. return 'NULL';
  1693. }
  1694. } elseif(!$this->new_record) {
  1695. if(in_array($field, $this->auto_update_timestamps)) {
  1696. return date($format);
  1697. } elseif($this->preserve_null_dates && is_null($value) && !stristr($field_info['flags'], "not_null")) {
  1698. return 'NULL';
  1699. }
  1700. }
  1701. }
  1702. }
  1703. }
  1704. }
  1705. return $value;
  1706. }
  1707.  
  1708. /**
  1709. * Update object attributes from list in argument
  1710. *
  1711. * The elements of $attributes are parsed and assigned to
  1712. * attributes of the ActiveRecord object. Date/time fields are
  1713. * treated according to the
  1714. * {@tutorial PHPonTrax/naming.pkg#naming.naming_forms}.
  1715. * @param string[] $attributes List of name => value pairs giving
  1716. * name and value of attributes to set.
  1717. * @uses $auto_save_associations
  1718. * @todo Figure out and document how datetime fields work
  1719. */
  1720. function update_attributes($attributes) {
  1721. //error_log('update_attributes()');
  1722. // Test each attribute to be updated
  1723. // and process according to its type
  1724. foreach($attributes as $field => $value) {
  1725. # datetime / date parts check
  1726. if(preg_match('/^\w+\(.*i\)$/i', $field)) {
  1727.  
  1728. // The name of this attribute ends in "(?i)"
  1729. // indicating that it's part of a date or time
  1730. // Accumulate all the pieces of a date and time in
  1731. // array $datetime_key. The keys in the array are
  1732. // the names of date/time attributes with the final
  1733. // "(?i)" stripped off.
  1734. $datetime_key = substr($field, 0, strpos($field, "("));
  1735. if( !isset($old_datetime_key)
  1736. || ($datetime_key != $old_datetime_key)) {
  1737.  
  1738. // This value of $datetime_key hasn't been seen
  1739. // before, so remember it.
  1740. $old_datetime_key = $datetime_key;
  1741.  
  1742. // $datetime_value accumulates the pieces of the
  1743. // date/time attribute $datetime_key
  1744. $datetime_value = "";
  1745. }
  1746.  
  1747. // Concatentate pieces of the attribute's value
  1748. // FIXME: this only works if the array elements
  1749. // are sorted by key. Is this guaranteed?
  1750. if(strstr($field, "2i") || strstr($field, "3i")) {
  1751. $datetime_value .= "-".$value;
  1752. } elseif(strstr($field, "4i")) {
  1753. $datetime_value .= " ".$value;
  1754. } elseif(strstr($field, "5i")) {
  1755. $datetime_value .= ":".$value;
  1756. } else {
  1757. $datetime_value .= $value;
  1758. }
  1759. $datetime_fields[$old_datetime_key] = $datetime_value;
  1760. # this elseif checks if first its an object if its parent is ActiveRecord
  1761. } elseif(is_object($value) && get_parent_class($value) == __CLASS__ && $this->auto_save_associations) {
  1762. if($association_type = $this->get_association_type($field)) {
  1763. $this->save_associations[$association_type][] = $value;
  1764. if($association_type == "belongs_to") {
  1765. $foreign_key = Inflector::singularize($value->table_name)."_id";
  1766. $this->$foreign_key = $value->id;
  1767. }
  1768. }
  1769. # this elseif checks if its an array of objects and if its parent is ActiveRecord
  1770. } elseif(is_array($value) && $this->auto_save_associations) {
  1771. if($association_type = $this->get_association_type($field)) {
  1772. $this->save_associations[$association_type][] = $value;
  1773. }
  1774. } else {
  1775.  
  1776. // Just a simple attribute, copy it
  1777. $this->$field = $value;
  1778. }
  1779. }
  1780.  
  1781. // If any date/time fields were found, assign the
  1782. // accumulated values to corresponding attributes
  1783. if(isset($datetime_fields)
  1784. && is_array($datetime_fields)) {
  1785. foreach($datetime_fields as $field => $value) {
  1786. //error_log("$field = $value");
  1787. $this->$field = $value;
  1788. }
  1789. }
  1790. $this->set_habtm_attributes($attributes);
  1791. }
  1792.  
  1793. /**
  1794. * Return pairs of column-name:column-value
  1795. *
  1796. * Return the contents of the object as an array of elements
  1797. * where the key is the column name and the value is the column
  1798. * value. Relies on a previous call to
  1799. * {@link set_content_columns()} for information about the format
  1800. * of a row in the table.
  1801. * @uses $content_columns
  1802. * @see set_content_columns
  1803. * @see quoted_attributes()
  1804. */
  1805. function get_attributes() {
  1806. $attributes = array();
  1807. if(is_array($this->content_columns)) {
  1808. foreach($this->content_columns as $column) {
  1809. //echo "attribute: $info[name] -> {$this->$info[name]}<br>";
  1810. $attributes[$column['name']] = $this->$column['name'];
  1811. }
  1812. }
  1813. return $attributes;
  1814. }
  1815.  
  1816. /**
  1817. * Return pairs of column-name:quoted-column-value
  1818. *
  1819. * Return pairs of column-name:quoted-column-value where the key
  1820. * is the column name and the value is the column value with
  1821. * automatic timestamp updating applied and characters special to
  1822. * SQL quoted.
  1823. *
  1824. * If $attributes is null or omitted, return all columns as
  1825. * currently stored in {@link content_columns()}. Otherwise,
  1826. * return the name:value pairs in $attributes.
  1827. * @param string[] $attributes Name:value pairs to return.
  1828. * If null or omitted, return the column names and values
  1829. * of the object as stored in $content_columns.
  1830. * @return string[]
  1831. * @uses get_attributes()
  1832. * @see set_content_columns()
  1833. */
  1834. function quoted_attributes($attributes = null) {
  1835. if(is_null($attributes)) {
  1836. $attributes = $this->get_attributes();
  1837. }
  1838. $return = array();
  1839. foreach ($attributes as $key => $value) {
  1840. $value = $this->check_datetime($key, $value);
  1841. # If the value isn't a function or null quote it.
  1842. if(!(preg_match('/^\w+\(.*\)$/U', $value)) && !(strcasecmp($value, 'NULL') == 0)) {
  1843. $value = str_replace("\\\"","\"",$value);
  1844. $value = str_replace("\'","'",$value);
  1845. $value = str_replace("\\\\","\\",$value);
  1846. $return[$key] = "'" . addslashes($value) . "'";
  1847. } else {
  1848. $return[$key] = $value;
  1849. }
  1850. //$return[$key] = $this->$db->quoteSmart($value);
  1851. }
  1852. return $return;
  1853. }
  1854.  
  1855. /**
  1856. * Return argument for a "WHERE" clause specifying this row
  1857. *
  1858. * Returns a string which specifies the column(s) and value(s)
  1859. * which describe the primary key of this row of the associated
  1860. * table. The primary key must be one or more attributes of the
  1861. * object and must be listed in {@link $content_columns} as
  1862. * columns in the row.
  1863. *
  1864. * Example: if $primary_keys = array("id", "ssn") and column "id"
  1865. * has value "5" and column "ssn" has value "123-45-6789" then
  1866. * the string "id = '5' AND ssn = '123-45-6789'" would be returned.
  1867. * @uses $primary_keys
  1868. * @uses quoted_attributes()
  1869. * @return string Column name = 'value' [ AND name = 'value']...
  1870. */
  1871. function get_primary_key_conditions() {
  1872. $conditions = null;
  1873. $attributes = $this->quoted_attributes();
  1874. if(count($attributes) > 0) {
  1875. $conditions = array();
  1876. # run through our fields and join them with their values
  1877. foreach($attributes as $key => $value) {
  1878. if(in_array($key, $this->primary_keys)) {
  1879. if(!is_numeric($value) && !strstr($value, "'")) {
  1880. $conditions[] = "$key = '$value'";
  1881. } else {
  1882. $conditions[] = "$key = $value";
  1883. }
  1884. }
  1885. }
  1886. $conditions = implode(" AND ", $conditions);
  1887. }
  1888. return $conditions;
  1889. }
  1890.  
  1891. /**
  1892. * Return column values of object formatted for SQL update statement
  1893. *
  1894. * Return a string containing the column names and values of this
  1895. * object in a format ready to be inserted in a SQL UPDATE
  1896. * statement. Automatic update has been applied to timestamps if
  1897. * enabled and characters special to SQL have been quoted.
  1898. * @uses quoted_attributes()
  1899. * @return string Column name = 'value', ... for all attributes
  1900. */
  1901. function get_updates_sql() {
  1902. $updates = null;
  1903. $attributes = $this->quoted_attributes();
  1904. if(count($attributes) > 0) {
  1905. $updates = array();
  1906. # run through our fields and join them with their values
  1907. foreach($attributes as $key => $value) {
  1908. if($key && $value && !in_array($key, $this->primary_keys)) {
  1909. $updates[] = "$key = $value";
  1910. }
  1911. }
  1912. $updates = implode(", ", $updates);
  1913. }
  1914. return $updates;
  1915. }
  1916.  
  1917. /**
  1918. * Set {@link $table_name} from the class name of this object
  1919. *
  1920. * By convention, the name of the database table represented by
  1921. * this object is derived from the name of the class.
  1922. * @uses Inflector::tableize()
  1923. */
  1924. function set_table_name_using_class_name() {
  1925. if(!$this->table_name) {
  1926. $this->table_name = Inflector::tableize(get_class($this));
  1927. }
  1928. }
  1929.  
  1930. /**
  1931. * Populate object with information about the table it represents
  1932. *
  1933. * Call {@link
  1934. * http://pear.php.net/manual/en/package.database.db.db-common.tableinfo.php
  1935. * DB_common::tableInfo()} to get a description of the table and
  1936. * store it in {@link $content_columns}. Add a more human
  1937. * friendly name to the element for each column.
  1938. * <b>FIXME: should throw an exception if tableInfo() fails</b>
  1939. * @uses $db
  1940. * @uses $content_columns
  1941. * @uses Inflector::humanize()
  1942. * @see __set()
  1943. * @param string $table_name Name of table to get information about
  1944. */
  1945. function set_content_columns($table_name) {
  1946. $this->content_columns = self::$db->tableInfo($table_name);
  1947. if(is_array($this->content_columns)) {
  1948. $i = 0;
  1949. foreach($this->content_columns as $column) {
  1950. $this->content_columns[$i++]['human_name'] = Inflector::humanize($column['name']);
  1951. }
  1952. }
  1953. }
  1954.  
  1955. /**
  1956. * Returns the autogenerated id from the last insert query
  1957. *
  1958. * @uses $db
  1959. * @uses is_error()
  1960. * @uses raise()
  1961. * @throws {@link ActiveRecordError}
  1962. */
  1963. function get_insert_id() {
  1964. $id = self::$db->getOne("SELECT LAST_INSERT_ID();");
  1965. if ($this->is_error($id)) {
  1966. $this->raise($id->getMessage());
  1967. }
  1968. return $id;
  1969. }
  1970.  
  1971. /**
  1972. * Open a database connection if one is not currently open
  1973. *
  1974. * The name of the database normally comes from
  1975. * $GLOBALS['TRAX_DB_SETTINGS'] which is set in {@link
  1976. * environment.php} by reading file config/database.ini. The
  1977. * database name may be overridden by assigning a different name
  1978. * to {@link $database_name}.
  1979. *
  1980. * If there is a connection now open, as indicated by the saved
  1981. * value of a DB object in $GLOBALS['ACTIVE_RECORD_DB'], and
  1982. * {@link force_reconnect} is not true, then set the database
  1983. * fetch mode and return.
  1984. *
  1985. * If there is no connection, open one and save a reference to
  1986. * it in $GLOBALS['ACTIVE_RECORD_DB'].
  1987. *
  1988. * @uses $db
  1989. * @uses $database_name
  1990. * @uses $force_reconnect
  1991. * @uses is_error()
  1992. * @throws {@link ActiveRecordError}
  1993. */
  1994. function establish_connection() {
  1995. # Connect to the database and throw an error if the connect fails.
  1996. if(!array_key_exists('ACTIVE_RECORD_DB',$GLOBALS)
  1997. || !is_object($GLOBALS['ACTIVE_RECORD_DB'])
  1998. || $this->force_reconnect) {
  1999. if(array_key_exists("use", $GLOBALS['TRAX_DB_SETTINGS'][TRAX_MODE])) {
  2000. $connection_settings = $GLOBALS['TRAX_DB_SETTINGS'][$GLOBALS['TRAX_DB_SETTINGS'][TRAX_MODE]['use']];
  2001. } else {
  2002. $connection_settings = $GLOBALS['TRAX_DB_SETTINGS'][TRAX_MODE];
  2003. }
  2004. # Override database name if param is set
  2005. if($this->database_name) {
  2006. $connection_settings['database'] = $this->database_name;
  2007. }
  2008. # Set optional Pear parameters
  2009. if(isset($connection_settings['persistent'])) {
  2010. $connection_options['persistent'] =
  2011. $connection_settings['persistent'];
  2012. }
  2013. $GLOBALS['ACTIVE_RECORD_DB'] =& DB::Connect($connection_settings, $connection_options);
  2014. }
  2015. if(!$this->is_error($GLOBALS['ACTIVE_RECORD_DB'])) {
  2016. self::$db = $GLOBALS['ACTIVE_RECORD_DB'];
  2017. } else {
  2018. $this->raise($GLOBALS['ACTIVE_RECORD_DB']->getMessage());
  2019. }
  2020. self::$db->setFetchMode($this->fetch_mode);
  2021. return self::$db;
  2022. }
  2023.  
  2024. /**
  2025. * Test whether argument is a PEAR Error object or a DB Error object.
  2026. *
  2027. * @param object $obj Object to test
  2028. * @return boolean Whether object is one of these two errors
  2029. */
  2030. function is_error($obj) {
  2031. if((PEAR::isError($obj)) || (DB::isError($obj))) {
  2032. return true;
  2033. } else {
  2034. return false;
  2035. }
  2036. }
  2037.  
  2038. /**
  2039. * Throw an exception describing an error in this object
  2040. *
  2041. * @throws {@link ActiveRecordError}
  2042. */
  2043. function raise($message) {
  2044. $error_message = "Model Class: ".get_class($this)."<br>";
  2045. $error_message .= "Error Message: ".$message;
  2046. throw new ActiveRecordError($error_message, "ActiveRecord Error", "500");
  2047. }
  2048.  
  2049. /**
  2050. * Add or overwrite description of an error to the list of errors
  2051. * @param string $error Error message text
  2052. * @param string $key Key to associate with the error (in the
  2053. * simple case, column name). If omitted, numeric keys will be
  2054. * assigned starting with 0. If specified and the key already
  2055. * exists in $errors, the old error message will be overwritten
  2056. * with the value of $error.
  2057. * @uses $errors
  2058. */
  2059. function add_error($error, $key = null) {
  2060. if(!is_null($key))
  2061. $this->errors[$key] = $error;
  2062. else
  2063. $this->errors[] = $error;
  2064. }
  2065.  
  2066. /**
  2067. * Return description of non-fatal errors
  2068. *
  2069. * @uses $errors
  2070. * @param boolean $return_string
  2071. * <ul>
  2072. * <li>true => Concatenate all error descriptions into a string
  2073. * using $seperator between elements and return the
  2074. * string</li>
  2075. * <li>false => Return the error descriptions as an array</li>
  2076. * </ul>
  2077. * @param string $seperator String to concatenate between error
  2078. * descriptions if $return_string == true
  2079. * @return mixed Error description(s), if any
  2080. */
  2081. function get_errors($return_string = false, $seperator = "<br>") {
  2082. if($return_string && count($this->errors) > 0) {
  2083. return implode($seperator, $this->errors);
  2084. } else {
  2085. return $this->errors;
  2086. }
  2087. }
  2088.  
  2089. /**
  2090. * Return errors as a string.
  2091. *
  2092. * Concatenate all error descriptions into a stringusing
  2093. * $seperator between elements and return the string.
  2094. * @param string $seperator String to concatenate between error
  2095. * descriptions
  2096. * @return string Concatenated error description(s), if any
  2097. */
  2098. function get_errors_as_string($seperator = "<br>") {
  2099. return $this->get_errors(true, $seperator);
  2100. }
  2101.  
  2102. /**
  2103. * Runs validation routines for update or create
  2104. *
  2105. * @uses after_validation();
  2106. * @uses after_validation_on_create();
  2107. * @uses after_validation_on_update();
  2108. * @uses before_validation();
  2109. * @uses before_validation_on_create();
  2110. * @uses before_validation_on_update();
  2111. * @uses $errors
  2112. * @uses $new_record
  2113. * @uses validate();
  2114. * @uses validate_model_attributes();
  2115. * @uses validate_on_create();
  2116. * @return boolean
  2117. * <ul>
  2118. * <li>true => Valid, no errors found.
  2119. * {@link $errors} is empty</li>
  2120. * <li>false => Not valid, errors in {@link $errors}</li>
  2121. * </ul>
  2122. */
  2123. function valid() {
  2124. # first clear the errors array
  2125. $this->errors = array();
  2126.  
  2127. if($this->new_record) {
  2128. $this->before_validation();
  2129. $this->before_validation_on_create();
  2130. $this->validate();
  2131. $this->validate_model_attributes();
  2132. $this->after_validation();
  2133. $this->validate_on_create();
  2134. $this->after_validation_on_create();
  2135. } else {
  2136. $this->before_validation();
  2137. $this->before_validation_on_update();
  2138. $this->validate();
  2139. $this->validate_model_attributes();
  2140. $this->after_validation();
  2141. $this->validate_on_update();
  2142. $this->after_validation_on_update();
  2143. }
  2144.  
  2145. return count($this->errors) ? false : true;
  2146. }
  2147.  
  2148. /**
  2149. * Call every method named "validate_*()" where * is a column name
  2150. *
  2151. * Find and call every method named "validate_something()" where
  2152. * "something" is the name of a column. The "validate_something()"
  2153. * functions are expected to return an array whose first element
  2154. * is true or false (indicating whether or not the validation
  2155. * succeeded), and whose second element is the error message to
  2156. * display if the first element is false.
  2157. *
  2158. * @return boolean
  2159. * <ul>
  2160. * <li>true => Valid, no errors found.
  2161. * {@link $errors} is empty</li>
  2162. * <li>false => Not valid, errors in {@link $errors}.
  2163. * $errors is an array whose keys are the names of columns,
  2164. * and the value of each key is the error message returned
  2165. * by the corresponding validate_*() method.</li>
  2166. * </ul>
  2167. * @uses $errors
  2168. * @uses get_attributes()
  2169. */
  2170. function validate_model_attributes() {
  2171. $validated_ok = true;
  2172. $attrs = $this->get_attributes();
  2173. $methods = get_class_methods(get_class($this));
  2174. foreach($methods as $method) {
  2175. if(preg_match('/^validate_(.+)/', $method, $matches)) {
  2176. # If we find, for example, a method named validate_name, then
  2177. # we know that that function is validating the 'name' attribute
  2178. # (as found in the (.+) part of the regular expression above).
  2179. $validate_on_attribute = $matches[1];
  2180. # Check to see if the string found (e.g. 'name') really is
  2181. # in the list of attributes for this object...
  2182. if(array_key_exists($validate_on_attribute, $attrs)) {
  2183. # ...if so, then call the method to see if it validates to true...
  2184. $result = $this->$method();
  2185. if(is_array($result)) {
  2186. # $result[0] is true if validation went ok, false otherwise
  2187. # $result[1] is the error message if validation failed
  2188. if($result[0] == false) {
  2189. # ... and if not, then validation failed
  2190. $validated_ok = false;
  2191. # Mark the corresponding entry in the error array by
  2192. # putting the error message in for the attribute,
  2193. # e.g. $this->errors['name'] = "can't be empty"
  2194. # when 'name' was an empty string.
  2195. $this->errors[$validate_on_attribute] = $result[1];
  2196. }
  2197. }
  2198. }
  2199. }
  2200. }
  2201. return $validated_ok;
  2202. }
  2203.  
  2204. /**
  2205. * Overwrite this method for validation checks on all saves and
  2206. * use $this->errors[] = "My error message."; or
  2207. * for invalid attributes $this->errors['attribute'] = "Attribute is invalid.";
  2208. * @todo Document this API
  2209. */
  2210. function validate() {}
  2211.  
  2212. /**
  2213. * Override this method for validation checks used only on creation.
  2214. * @todo Document this API
  2215. */
  2216. function validate_on_create() {}
  2217.  
  2218. /**
  2219. * Override this method for validation checks used only on updates.
  2220. * @todo Document this API
  2221. */
  2222. function validate_on_update() {}
  2223.  
  2224. /**
  2225. * Is called before validate().
  2226. * @todo Document this API
  2227. */
  2228. function before_validation() {}
  2229.  
  2230. /**
  2231. * Is called after validate().
  2232. * @todo Document this API
  2233. */
  2234. function after_validation() {}
  2235.  
  2236. /**
  2237. * Is called before validate() on new objects that haven't been saved yet (no record exists).
  2238. * @todo Document this API
  2239. */
  2240. function before_validation_on_create() {}
  2241.  
  2242. /**
  2243. * Is called after validate() on new objects that haven't been saved yet (no record exists).
  2244. * @todo Document this API
  2245. */
  2246. function after_validation_on_create() {}
  2247.  
  2248. /**
  2249. * Is called before validate() on existing objects that has a record.
  2250. * @todo Document this API
  2251. */
  2252. function before_validation_on_update() {}
  2253.  
  2254. /**
  2255. * Is called after validate() on existing objects that has a record.
  2256. * @todo Document this API
  2257. */
  2258. function after_validation_on_update() {}
  2259.  
  2260. /**
  2261. * Is called before save() (regardless of whether its a create or update save)
  2262. * @todo Document this API
  2263. */
  2264. function before_save() {}
  2265.  
  2266. /**
  2267. * Is called after save (regardless of whether its a create or update save).
  2268. * @todo Document this API
  2269. */
  2270. function after_save() {}
  2271.  
  2272. /**
  2273. * Is called before save() on new objects that havent been saved yet (no record exists).
  2274. * @todo Document this API
  2275. */
  2276. function before_create() {}
  2277.  
  2278. /**
  2279. * Is called after save() on new objects that havent been saved yet (no record exists).
  2280. * @todo Document this API
  2281. */
  2282. function after_create() {}
  2283.  
  2284. /**
  2285. * Is called before save() on existing objects that has a record.
  2286. * @todo Document this API
  2287. */
  2288. function before_update() {}
  2289.  
  2290. /**
  2291. * Is called after save() on existing objects that has a record.
  2292. * @todo Document this API
  2293. */
  2294. function after_update() {}
  2295.  
  2296. /**
  2297. * Is called before delete().
  2298. * @todo Document this API
  2299. */
  2300. function before_delete() {}
  2301.  
  2302. /**
  2303. * Is called after delete().
  2304. * @todo Document this API
  2305. */
  2306. function after_delete() {}
  2307.  
  2308. /**
  2309. * Log SQL query in development mode
  2310. *
  2311. * If running in development mode, log the query to $GLOBAL
  2312. * @param string SQL to be logged
  2313. */
  2314. function log_query($sql) {
  2315. if(TRAX_MODE == "development" && $sql) {
  2316. $GLOBALS['ACTIVE_RECORD_SQL_LOG'][] = $sql;
  2317. }
  2318. }
  2319.  
  2320. /**
  2321. * Paging html functions
  2322. * @todo Document this API
  2323. */
  2324. function limit_select($controller =null, $additional_query = null) {
  2325. if($this->pages > 0) {
  2326. $html = "
  2327. <select name=\"per_page\" onChange=\"document.location = '?$this->paging_extra_params&per_page=' + this.options[this.selectedIndex].value;\">
  2328. <option value=\"$this->rows_per_page\" selected>per page:</option>
  2329. <option value=10>10</option>
  2330. <option value=20>20</option>
  2331. <option value=50>50</option>
  2332. <option value=100>100</option>
  2333. <option value=999999999>ALL</option>
  2334. </select>
  2335. ";
  2336. }
  2337. return $html;
  2338. }
  2339.  
  2340. /**
  2341. * @todo Document this API
  2342. *
  2343. * @return string HTML to link to previous and next pages
  2344. * @uses $display
  2345. * @uses $page
  2346. * @uses $pages
  2347. * @uses $paging_extra_params
  2348. * @uses rows_per_page
  2349. */
  2350. function page_list(){
  2351. $page_list = "";
  2352.  
  2353. /* Print the first and previous page links if necessary */
  2354. if(($this->page != 1) && ($this->page))
  2355. $page_list .= "<a href=\"?$this->paging_extra_params&page=1&per_page=$this->rows_per_page\" class=\"page_list\" title=\"First Page\"><<</a> ";
  2356.  
  2357. if(($this->page-1) > 0)
  2358. $page_list .= "<a href=\"?$this->paging_extra_params&page=".($this->page-1)."&per_page=$this->rows_per_page\" class=\"page_list\" title=\"Previous Page\"><</a> ";
  2359.  
  2360. if($this->pages < $this->display)
  2361. $this->display = $this->pages;
  2362.  
  2363. if($this->page == $this->pages) {
  2364. if($this->pages - $this->display == 0)
  2365. $start = 1;
  2366. else
  2367. $start = $this->pages - $this->display;
  2368. $max = $this->pages;
  2369. } else {
  2370. if($this->page >= $this->display) {
  2371. $start = $this->page - ($this->display / 2);
  2372. $max = $this->page + (($this->display / 2)-1);
  2373. } else {
  2374. $start = 1;
  2375. $max = $this->display;
  2376. }
  2377. }
  2378.  
  2379. if($max >= $this->pages)
  2380. $max = $this->pages;
  2381.  
  2382. /* Print the numeric page list; make the current page unlinked and bold */
  2383. if($max != 1) {
  2384. for ($i=$start; $i<=$max; $i++) {
  2385. if ($i == $this->page)
  2386. $page_list .= "<span class=\"pageList\"><b>".$i."</b></span>";
  2387. else
  2388. $page_list .= "<a href=\"?$this->paging_extra_params&page=$i&per_page=$this->rows_per_page\" class=\"page_list\" title=\"Page ".$i."\">".$i."</a>";
  2389.  
  2390. $page_list .= " ";
  2391. }
  2392. }
  2393.  
  2394. /* Print the Next and Last page links if necessary */
  2395. if(($this->page+1) <= $this->pages)
  2396. $page_list .= "<a href=\"?$this->paging_extra_params&page=".($this->page+1)."&per_page=$this->rows_per_page\" class=\"page_list\" title=\"Next Page\">></a> ";
  2397.  
  2398. if(($this->page != $this->pages) && ($this->pages != 0))
  2399. $page_list .= "<a href=\"?$this->paging_extra_params&page=".$this->pages."&per_page=$this->rows_per_page\" class=\"page_list\" title=\"Last Page\">>></a> ";
  2400.  
  2401. $page_list .= "\n";
  2402.  
  2403. //error_log("Page list=[$page_list]");
  2404. return $page_list;
  2405. }
  2406.  
  2407. }
  2408.  
  2409. // -- set Emacs parameters --
  2410. // Local variables:
  2411. // tab-width: 4
  2412. // c-basic-offset: 4
  2413. // c-hanging-comment-ender-p: nil
  2414. // indent-tabs-mode: nil
  2415. // End:

Documentation generated on Thu, 04 May 2006 19:47:11 -0600 by phpDocumentor 1.3.0RC4