PageRenderTime 48ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/modules/sprig/classes/sprig/core.php

https://bitbucket.org/seyar/parshin.local
PHP | 1341 lines | 825 code | 191 blank | 325 comment | 107 complexity | bfb1725c77cfd4d1fe748a61ad682ebf MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * Sprig database modeling system.
  4. *
  5. * @package Sprig
  6. * @author Woody Gilk
  7. * @copyright (c) 2009 Woody Gilk
  8. * @license MIT
  9. */
  10. abstract class Sprig_Core {
  11. // Model many-to-many relations
  12. protected static $_relations;
  13. /**
  14. * Load an empty sprig model.
  15. *
  16. * @param string model name
  17. * @param array values to pre-populate the model
  18. * @return Sprig
  19. */
  20. public static function factory($name, array $values = NULL)
  21. {
  22. $model = 'Model_'.$name;
  23. $model = new $model;
  24. if ($values)
  25. {
  26. $model->values($values);
  27. }
  28. return $model;
  29. }
  30. /**
  31. * @var string model name
  32. */
  33. protected $_model;
  34. /**
  35. * @var string database instance name
  36. */
  37. protected $_db = 'default';
  38. /**
  39. * @var string database table name
  40. */
  41. protected $_table;
  42. /**
  43. * @var array field list (name => object)
  44. */
  45. protected $_fields = array();
  46. /**
  47. * @var mixed primary key string or array (for composite keys)
  48. */
  49. protected $_primary_key;
  50. /**
  51. * @var string title key string (for select lists)
  52. */
  53. protected $_title_key = 'name';
  54. /**
  55. * @var array default sorting parameters
  56. */
  57. protected $_sorting;
  58. // Initialization status
  59. protected $_init = FALSE;
  60. // Object state
  61. protected $_state = 'new';
  62. // Original object data
  63. protected $_original = array();
  64. // Changed object data
  65. protected $_changed = array();
  66. // Related object data
  67. protected $_related = array();
  68. /**
  69. * Initialize the fields and add validation rules based on field properties.
  70. *
  71. * @return void
  72. */
  73. protected function __construct()
  74. {
  75. if ($this->_init)
  76. {
  77. if ($this->state() === 'loading')
  78. {
  79. // Object loading via mysql_fetch_object or similar has finished
  80. $this->state('loaded');
  81. }
  82. // Can only be called once
  83. return;
  84. }
  85. // Initialization has been started
  86. $this->_init = TRUE;
  87. // Set up the fields
  88. $this->_init();
  89. if ( ! $this->_model)
  90. {
  91. // Set the model name based on the class name
  92. $this->_model = strtolower(substr(get_class($this), 6));
  93. }
  94. if ( ! $this->_table)
  95. {
  96. // Set the table name to the plural model name
  97. $this->_table = inflector::plural($this->_model);
  98. }
  99. foreach ($this->_fields as $name => $field)
  100. {
  101. if ($field->primary === TRUE)
  102. {
  103. if ( ! $this->_primary_key)
  104. {
  105. // This is the primary key
  106. $this->_primary_key = $name;
  107. }
  108. else
  109. {
  110. if (is_string($this->_primary_key))
  111. {
  112. // More than one primary key found, create a list of keys
  113. $this->_primary_key = array($this->_primary_key);
  114. }
  115. // Add this key to the list
  116. $this->_primary_key[] = $name;
  117. }
  118. }
  119. }
  120. foreach ($this->_fields as $name => $field)
  121. {
  122. if ($field instanceof Sprig_Field_ForeignKey AND ! $field->model)
  123. {
  124. if ($field instanceof Sprig_Field_HasMany)
  125. {
  126. $field->model = Inflector::singular($name);
  127. }
  128. else
  129. {
  130. $field->model = $name;
  131. }
  132. }
  133. if ($field instanceof Sprig_Field_ManyToMany)
  134. {
  135. if ( ! $field->through)
  136. {
  137. // Get the model names for the relation pair
  138. $pair = array(strtolower($this->_model), strtolower($field->model));
  139. // Sort the model names alphabetically
  140. sort($pair);
  141. // Join the model names to get the relation name
  142. $pair = implode('_', $pair);
  143. if ( ! isset(Sprig::$_relations[$pair]))
  144. {
  145. // Must set the pair key before loading the related model
  146. // or we will fall into an infinite recursion loop
  147. Sprig::$_relations[$pair] = TRUE;
  148. $tables = array($this->table(), Sprig::factory($field->model)->table());
  149. // Sort the table names alphabetically
  150. sort($tables);
  151. // Join the table names to get the table name
  152. Sprig::$_relations[$pair] = implode('_', $tables);
  153. }
  154. // Assign by reference so that changes to the pivot table
  155. // will carry over to all models
  156. $field->through =& Sprig::$_relations[$pair];
  157. }
  158. }
  159. if ( ! $field->column)
  160. {
  161. // Create the key based on the field name
  162. if ($field instanceof Sprig_Field_BelongsTo)
  163. {
  164. $field->column = Sprig::factory($field->model)->fk();
  165. }
  166. elseif ($field instanceof Sprig_Field_HasOne)
  167. {
  168. $field->column = $this->fk();
  169. }
  170. elseif ($field instanceof Sprig_Field_ForeignKey)
  171. {
  172. // This field is probably a Many and does not need a column
  173. }
  174. else
  175. {
  176. $field->column = $name;
  177. }
  178. }
  179. if ( ! $field->label)
  180. {
  181. $field->label = Inflector::humanize($name);
  182. }
  183. if ($field->null)
  184. {
  185. // Fields that allow NULL values must accept empty values
  186. $field->empty = TRUE;
  187. }
  188. if ($field->editable)
  189. {
  190. if ( ! $field->empty AND ! isset($field->rules['not_empty']))
  191. {
  192. // This field must not be empty
  193. $field->rules['not_empty'] = NULL;
  194. }
  195. if ($field->unique)
  196. {
  197. // Field must be a unique value
  198. $field->callbacks[] = array($this, '_unique_field');
  199. }
  200. if ($field->choices AND ! isset($field->rules['in_array']))
  201. {
  202. // Field must be one of the available choices
  203. $field->rules['in_array'] = array(array_keys($field->choices));
  204. }
  205. if ( ! empty($field->min_length))
  206. {
  207. $field->rules['min_length'] = array($field->min_length);
  208. }
  209. if ( ! empty($field->max_length))
  210. {
  211. $field->rules['max_length'] = array($field->max_length);
  212. }
  213. }
  214. if ($field instanceof Sprig_Field_BelongsTo OR ! $field instanceof Sprig_Field_ForeignKey)
  215. {
  216. // Set the default value for any field that is stored in the database
  217. $this->_original[$name] = $field->value($field->default);
  218. }
  219. }
  220. }
  221. /**
  222. * Returns the model name.
  223. *
  224. * @return string
  225. */
  226. public function __toString()
  227. {
  228. return $this->_model;
  229. }
  230. /**
  231. * Get the value of a field.
  232. *
  233. * @throws Sprig_Exception field does not exist
  234. * @param string field name
  235. * @return mixed
  236. */
  237. public function __get($name)
  238. {
  239. if ( ! $this->_init)
  240. {
  241. // The constructor must always be called first
  242. $this->__construct();
  243. // This object is about to be loaded by mysql_fetch_object() or similar
  244. $this->state('loading');
  245. }
  246. if ( ! isset($this->_fields[$name]))
  247. {
  248. throw new Sprig_Exception(':name model does not have a field :field',
  249. array(':name' => get_class($this), ':field' => $name));
  250. }
  251. if (isset($this->_related[$name]))
  252. {
  253. // Shortcut to any related object
  254. return $this->_related[$name];
  255. }
  256. $field = $this->_fields[$name];
  257. if ($this->changed($name))
  258. {
  259. $value = $this->_changed[$name];
  260. }
  261. elseif (array_key_exists($name, $this->_original))
  262. {
  263. $value = $this->_original[$name];
  264. }
  265. if ($field instanceof Sprig_Field_ForeignKey)
  266. {
  267. if ( ! isset($this->_related[$name]))
  268. {
  269. $model = Sprig::factory($field->model);
  270. if ($field instanceof Sprig_Field_HasMany)
  271. {
  272. if ($field instanceof Sprig_Field_ManyToMany)
  273. {
  274. if (isset($value))
  275. {
  276. if (empty($value))
  277. {
  278. return new Database_Result_Cached(array(), '');
  279. }
  280. else
  281. {
  282. $query = DB::select()
  283. ->where($model->pk(), 'IN', $value);
  284. }
  285. }
  286. else
  287. {
  288. $query = DB::select()
  289. ->join($field->through)
  290. ->on($model->fk($field->through), '=', $model->pk(TRUE))
  291. ->where($this->fk($field->through), '=', $this->{$this->_primary_key});
  292. }
  293. }
  294. else
  295. {
  296. if (isset($value))
  297. {
  298. $query = DB::select()
  299. ->where($model->pk(), '=', $value);
  300. }
  301. else
  302. {
  303. $query = DB::select()
  304. ->where($this->fk(), '=', $this->{$this->_primary_key});
  305. }
  306. }
  307. $related = $model->load($query, NULL);
  308. if ( ! $this->changed($name))
  309. {
  310. // We can assume this is the original value because no
  311. // changed value exists
  312. $this->_original[$name] = $field->value($related);
  313. }
  314. }
  315. elseif ($field instanceof Sprig_Field_BelongsTo)
  316. {
  317. $related = $model->values(array($model->pk() => $value));
  318. }
  319. elseif ($field instanceof Sprig_Field_HasOne)
  320. {
  321. $related = $model->values(array($this->_model => $this->{$this->_primary_key}));
  322. }
  323. $value = $this->_related[$name] = $related;
  324. }
  325. }
  326. return $value;
  327. }
  328. /**
  329. * Set the value of a field.
  330. *
  331. * @throws Sprig_Exception field does not exist
  332. * @param string field name
  333. * @param mixed new field value
  334. * @return void
  335. */
  336. public function __set($name, $value)
  337. {
  338. if ( ! $this->_init)
  339. {
  340. // The constructor must always be called first
  341. $this->__construct();
  342. // This object is about to be loaded by mysql_fetch_object() or similar
  343. $this->state('loading');
  344. }
  345. if ( ! isset($this->_fields[$name]))
  346. {
  347. throw new Sprig_Exception(':name model does not have a field :field',
  348. array(':name' => get_class($this), ':field' => $name));
  349. }
  350. // Get the field object
  351. $field = $this->_fields[$name];
  352. if ($this->state() === 'loading')
  353. {
  354. // Set the original value directly
  355. $this->_original[$name] = $field->value($value);
  356. // No extra processing necessary
  357. return;
  358. }
  359. elseif ($field instanceof Sprig_Field_ManyToMany)
  360. {
  361. if ( ! isset($this->_original[$name]))
  362. {
  363. $model = Sprig::factory($field->model);
  364. $result = DB::select($model->fk())
  365. ->from($field->through)
  366. ->where($this->fk(), '=', $this->{$this->_primary_key})
  367. ->execute($this->_db);
  368. // The original value for the relationship must be defined
  369. // before we can tell if the value has been changed
  370. $this->_original[$name] = $field->value($result->as_array(NULL, $model->fk()));
  371. }
  372. }
  373. elseif ($field instanceof Sprig_Field_HasMany)
  374. {
  375. foreach ($value as $key => $val)
  376. {
  377. if ( ! $val instanceof Sprig)
  378. {
  379. $model = Sprig::factory($field->model);
  380. $pk = $model->pk();
  381. if ( ! is_array($val))
  382. {
  383. // Assume the value is a primary key
  384. $val = array($pk => $val);
  385. }
  386. if (isset($val[$pk]))
  387. {
  388. // Load the record so that changed values can be determined
  389. $model->values(array($pk => $val[$pk]))->load();
  390. }
  391. $value[$key] = $model->values($val);
  392. }
  393. }
  394. // Set the related objects to this value
  395. $this->_related[$name] = $value;
  396. // No extra processing necessary
  397. return;
  398. }
  399. elseif ($field instanceof Sprig_Field_BelongsTo)
  400. {
  401. // Pass
  402. }
  403. elseif ($field instanceof Sprig_Field_ForeignKey)
  404. {
  405. throw new Sprig_Exception('Cannot change relationship of :model->:field using __set()',
  406. array(':model' => $this->_model, ':field' => $name));
  407. }
  408. // Get the correct type of value
  409. $changed = $field->value($value);
  410. if (isset($field->hash_with) AND $changed)
  411. {
  412. $changed = call_user_func($field->hash_with, $changed);
  413. }
  414. if ($changed !== $this->_original[$name])
  415. {
  416. if (isset($this->_related[$name]))
  417. {
  418. // Clear stale related objects
  419. unset($this->_related[$name]);
  420. }
  421. // Set a changed value
  422. $this->_changed[$name] = $changed;
  423. if ($field instanceof Sprig_Field_ForeignKey AND is_object($value))
  424. {
  425. // Store the related object for later use
  426. $this->_related[$name] = $value;
  427. }
  428. }
  429. }
  430. /**
  431. * Check if a value exists within the mode.
  432. *
  433. * @param string field name
  434. * @return boolean
  435. */
  436. public function __isset($name)
  437. {
  438. return isset($this->_fields[$name]);
  439. }
  440. /**
  441. * Unset the changed the value of a field.
  442. *
  443. * @throws Sprig_Exception field does not exist
  444. * @param string field name
  445. * @return void
  446. */
  447. public function __unset($name)
  448. {
  449. if ( ! $this->_init)
  450. {
  451. // The constructor must always be called first
  452. $this->__construct();
  453. }
  454. if ( ! isset($this->_fields[$name]))
  455. {
  456. throw new Sprig_Exception(':name model does not have a field :field',
  457. array(':name' => get_class($this), ':field' => $name));
  458. }
  459. $field = $this->_fields[$name];
  460. if ($field->in_db)
  461. {
  462. // Set the original value back to the default
  463. $this->_original[$name] = $field->value($field->default);
  464. }
  465. // Remove any changed value
  466. unset($this->_changed[$name]);
  467. }
  468. /**
  469. * Returns the primary key of the model, optionally with a table name.
  470. *
  471. * @param string table name, TRUE for the model table
  472. * @return string
  473. */
  474. public function pk($table = NULL)
  475. {
  476. if ($table)
  477. {
  478. if ($table === TRUE)
  479. {
  480. $table = $this->_table;
  481. }
  482. return $table.'.'.$this->_primary_key;
  483. }
  484. return $this->_primary_key;
  485. }
  486. /**
  487. * Returns the foreign key of the model, optionally with a table name.
  488. *
  489. * @param string table name, TRUE for the model table
  490. * @return string
  491. */
  492. public function fk($table = NULL)
  493. {
  494. $key = $this->_model.'_'.$this->_primary_key;
  495. if ($table)
  496. {
  497. if ($table === TRUE)
  498. {
  499. $table = $this->_table;
  500. }
  501. return $table.'.'.$key;
  502. }
  503. return $key;
  504. }
  505. /**
  506. * Returns the title key of the model, optionally with a table name.
  507. *
  508. * @param string table name, TRUE for the model table
  509. * @return string
  510. */
  511. public function tk($table = NULL)
  512. {
  513. if ($table)
  514. {
  515. if ($table === TRUE)
  516. {
  517. $table = $this->_table;
  518. }
  519. return $table.'.'.$this->_title_key;
  520. }
  521. return $this->_title_key;
  522. }
  523. /**
  524. * Gets and sets the database instance used for this model.
  525. *
  526. * @return string
  527. */
  528. public function db($db = NULL)
  529. {
  530. if ($db)
  531. {
  532. $this->_db = $db;
  533. }
  534. return $this->_db;
  535. }
  536. /**
  537. * Gets and sets the table name of the model.
  538. *
  539. * @param string new table name
  540. * @return string table name
  541. */
  542. public function table($table = NULL)
  543. {
  544. if ($table)
  545. {
  546. $this->_table = $table;
  547. }
  548. return $this->_table;
  549. }
  550. /**
  551. * Load all of the values in an associative array. Ignores all fields are
  552. * not in the model.
  553. *
  554. * @param array field => value pairs
  555. * @return $this
  556. */
  557. public function values(array $values)
  558. {
  559. // Remove all values which do not have a corresponding field
  560. $values = array_intersect_key($values, $this->_fields);
  561. foreach ($values as $field => $value)
  562. {
  563. $this->$field = $value;
  564. }
  565. return $this;
  566. }
  567. /**
  568. * Get the model data as an associative array.
  569. *
  570. * @return array field => value
  571. */
  572. public function as_array($verbose = FALSE)
  573. {
  574. $data = array_merge($this->_original, $this->_changed);
  575. if ($verbose)
  576. {
  577. foreach ($data as $field => $value)
  578. {
  579. // Convert each field to the verbose value
  580. $data[$field] = $this->_fields[$field]->verbose($value);
  581. }
  582. }
  583. return $data;
  584. }
  585. /**
  586. * Get all of the records for this table as an associative array.
  587. *
  588. * @param string array key, defaults to the primary key
  589. * @param string array value, defaults to the title key
  590. * @return array key => value
  591. */
  592. public function select_list($key = NULL, $value = NULL)
  593. {
  594. if ( ! $key)
  595. {
  596. $key = $this->pk();
  597. }
  598. if ( ! $value)
  599. {
  600. $value = $this->tk();
  601. }
  602. $query = DB::select($key, $value)
  603. ->from($this->_table);
  604. if ($this->_sorting)
  605. {
  606. foreach ($this->_sorting as $field => $direction)
  607. {
  608. $query->order_by($field, $direction);
  609. }
  610. }
  611. return $query
  612. ->execute($this->_db)
  613. ->as_array($key, $value);
  614. }
  615. /**
  616. * Get or set the model status.
  617. *
  618. * Setting the model status can have side effects. Changing the state to
  619. * "loaded" will merge the currently changed data with the original data.
  620. * Changing to "new" will reset the original data to the default values.
  621. * Setting a "deleted" state will reset the changed data.
  622. *
  623. * Possible model states:
  624. *
  625. * - new: record has not been created
  626. * - deleted: record has been deleted
  627. * - loaded: record has been loaded
  628. *
  629. * @param string new object status
  630. * @return string when getting
  631. * @return $this when setting
  632. */
  633. public function state($state = NULL)
  634. {
  635. if ($state)
  636. {
  637. switch ($state)
  638. {
  639. case 'new':
  640. // Reset original data
  641. $this->_original = Sprig::factory($this->_model)->as_array();
  642. break;
  643. case 'loaded':
  644. // Merge the changed data into the original data
  645. $this->_original = array_merge($this->_original, $this->_changed);
  646. $this->_changed = array();
  647. break;
  648. case 'deleted':
  649. case 'loading':
  650. // Pass
  651. break;
  652. default:
  653. throw new Sprig_Exception('Unknown model state: :state', array(':state' => $state));
  654. break;
  655. }
  656. // Set the new state
  657. $this->_state = $state;
  658. return $this;
  659. }
  660. return $this->_state;
  661. }
  662. /**
  663. * Object data loaded status.
  664. *
  665. * @return boolean
  666. */
  667. public function loaded()
  668. {
  669. return $this->_state === 'loaded';
  670. }
  671. /**
  672. * Get all of the changed fields as an associative array.
  673. *
  674. * @return array field => value
  675. */
  676. public function changed($field = NULL)
  677. {
  678. if ($field === NULL)
  679. {
  680. // Note that array_diff_assoc() can't be used here because it
  681. // assumes that any two array values are the same... WTF!
  682. $changed = $this->as_array();
  683. foreach ($changed as $field => $value)
  684. {
  685. if ( ! array_key_exists($field, $this->_changed))
  686. {
  687. unset($changed[$field]);
  688. }
  689. }
  690. return $changed;
  691. }
  692. else
  693. {
  694. return array_key_exists($field, $this->_changed);
  695. }
  696. }
  697. /**
  698. * Get a single field object.
  699. *
  700. * @return Sprig_Field
  701. */
  702. public function field($name)
  703. {
  704. return $this->_fields[$name];
  705. }
  706. /**
  707. * Get all fields as an associative array.
  708. *
  709. * @return array name => object
  710. */
  711. public function fields()
  712. {
  713. return $this->_fields;
  714. }
  715. /**
  716. * Return a single field input.
  717. *
  718. * @param string field name
  719. * @param array input attributes
  720. * @return string
  721. */
  722. public function input($name, array $attr = NULL)
  723. {
  724. $field = $this->_fields[$name];
  725. if ($attr === NULL)
  726. {
  727. $attr = $field->attributes;
  728. }
  729. return $field->input($name, $this->$name, $attr);
  730. }
  731. /**
  732. * Get all fields as an array of inputs.
  733. *
  734. * @param boolean use the input label as the array key
  735. * @return array label => input
  736. */
  737. public function inputs($labels = TRUE)
  738. {
  739. $inputs = array();
  740. foreach ($this->_fields as $name => $field)
  741. {
  742. if ($field->editable)
  743. {
  744. if ($labels === TRUE)
  745. {
  746. $key = $field->label($name);
  747. }
  748. else
  749. {
  750. $key = $name;
  751. }
  752. $inputs[$key] = $field->input($name, $this->$name, $field->attributes);
  753. }
  754. }
  755. return $inputs;
  756. }
  757. /**
  758. * Return a single field label.
  759. *
  760. * @param string field name
  761. * @param array label attributes
  762. * @return string
  763. */
  764. public function label($field, array $attr = NULL)
  765. {
  766. return $this->_fields[$field]->label($field, $attr);
  767. }
  768. /**
  769. * Return a single field value in verbose form.
  770. *
  771. * @param string field name
  772. * @return string
  773. */
  774. public function verbose($field)
  775. {
  776. return $this->_fields[$field]->verbose($this->$field);
  777. }
  778. /**
  779. * Count the number of records using the current data.
  780. *
  781. * @param object any Database_Query_Builder_Select, NULL for none
  782. * @return $this
  783. */
  784. public function count(Database_Query_Builder_Select $query = NULL)
  785. {
  786. if ( ! $query)
  787. {
  788. $query = DB::select();
  789. }
  790. $table = is_array($this->_table) ? $this->_table[1] : $this->_table;
  791. if ($changed = $this->changed())
  792. {
  793. foreach ($changed as $field => $value)
  794. {
  795. $field = $this->_fields[$field];
  796. if ( ! $field->in_db)
  797. {
  798. continue;
  799. }
  800. $query->where("{$table}.{$field->column}", '=', $value);
  801. }
  802. }
  803. return $query->select(array('COUNT("*")', 'total'))
  804. ->from($this->_table)
  805. ->execute($this->_db)
  806. ->get('total');
  807. }
  808. /**
  809. * Load a single record using the current data.
  810. *
  811. * @param object any Database_Query_Builder_Select, NULL for none
  812. * @param integer number of records to load, FALSE for all
  813. * @return $this
  814. */
  815. public function load(Database_Query_Builder_Select $query = NULL, $limit = 1)
  816. {
  817. // Load changed values as search parameters
  818. $changed = $this->changed();
  819. if ( ! $query)
  820. {
  821. $query = DB::select();
  822. }
  823. $query->from($this->_table);
  824. $table = is_array($this->_table) ? $this->_table[1] : $this->_table;
  825. foreach ($this->_fields as $name => $field)
  826. {
  827. if ( ! $field->in_db)
  828. {
  829. // Multiple relations cannot be loaded this way
  830. continue;
  831. }
  832. if ($name === $field->column)
  833. {
  834. $query->select("{$table}.{$name}");
  835. }
  836. else
  837. {
  838. $query->select(array("{$table}.{$field->column}", $name));
  839. }
  840. if (array_key_exists($name, $changed))
  841. {
  842. $query->where("{$table}.{$field->column}", '=', $changed[$name]);
  843. }
  844. }
  845. if ($limit)
  846. {
  847. $query->limit($limit);
  848. }
  849. if ($this->_sorting)
  850. {
  851. foreach ($this->_sorting as $field => $direction)
  852. {
  853. $query->order_by($field, $direction);
  854. }
  855. }
  856. if ($limit === 1)
  857. {
  858. $result = $query
  859. ->execute($this->_db);
  860. if (count($result))
  861. {
  862. $this->values($result[0])->state('loaded');
  863. }
  864. return $this;
  865. }
  866. else
  867. {
  868. return $query
  869. ->as_object(get_class($this))
  870. ->execute($this->_db);
  871. }
  872. }
  873. /**
  874. * Create a new record using the current data.
  875. *
  876. * @uses Sprig::check()
  877. * @return $this
  878. */
  879. public function create()
  880. {
  881. foreach ($this->_fields as $name => $field)
  882. {
  883. if ($field instanceof Sprig_Field_Timestamp AND $field->auto_now_create)
  884. {
  885. // Set the value to the current timestamp
  886. $this->$name = time();
  887. }
  888. }
  889. // Check the all current data
  890. $data = $this->check($this->as_array());
  891. $values = $relations = array();
  892. foreach ($data as $name => $value)
  893. {
  894. $field = $this->_fields[$name];
  895. if ($field instanceof Sprig_Field_Auto OR ! $field->in_db )
  896. {
  897. if ($field instanceof Sprig_Field_ManyToMany)
  898. {
  899. $relations[$name] = $value;
  900. }
  901. // Skip all auto-increment fields or where in_db is false
  902. continue;
  903. }
  904. // Change the field name to the column name
  905. $values[$field->column] = $value;
  906. }
  907. list($id) = DB::insert($this->_table, array_keys($values))
  908. ->values($values)
  909. ->execute($this->_db);
  910. if (is_array($this->_primary_key))
  911. {
  912. foreach ($this->_primary_key as $name)
  913. {
  914. if ($this->_fields[$name] instanceof Sprig_Field_Auto)
  915. {
  916. // Set the auto-increment primary key to the insert id
  917. $this->$name = $id;
  918. // There can only be 1 auto-increment column per model
  919. break;
  920. }
  921. }
  922. }
  923. elseif ($this->_fields[$this->_primary_key] instanceof Sprig_Field_Auto)
  924. {
  925. $this->{$this->_primary_key} = $id;
  926. }
  927. // Object is now loaded
  928. $this->state('loaded');
  929. if ($relations)
  930. {
  931. foreach ($relations as $name => $value)
  932. {
  933. $field = $this->_fields[$name];
  934. $model = Sprig::factory($field->model);
  935. foreach ($value as $id)
  936. {
  937. DB::insert($field->through, array($this->fk(), $model->fk()))
  938. ->values(array($this->{$this->_primary_key}, $id))
  939. ->execute($this->_db);
  940. }
  941. }
  942. }
  943. return $this;
  944. }
  945. /**
  946. * Update the current record using the current data.
  947. *
  948. * @uses Sprig::check()
  949. * @return $this
  950. */
  951. public function update()
  952. {
  953. if ($this->changed())
  954. {
  955. foreach ($this->_fields as $name => $field)
  956. {
  957. if ($field instanceof Sprig_Field_Timestamp AND $field->auto_now_update)
  958. {
  959. // Set the value to the current timestamp
  960. $this->$name = time();
  961. }
  962. }
  963. // Check the updated data
  964. $data = $this->check($this->changed());
  965. $values = $relations = array();
  966. foreach ($data as $name => $value)
  967. {
  968. $field = $this->_fields[$name];
  969. if ( ! $field->in_db)
  970. {
  971. if ($field instanceof Sprig_Field_ManyToMany)
  972. {
  973. // Relationships have been changed
  974. $relations[$name] = $value;
  975. }
  976. // Skip all fields that are not in the database
  977. continue;
  978. }
  979. // Change the field name to the column name
  980. $values[$field->column] = $value;
  981. }
  982. if ($values)
  983. {
  984. $query = DB::update($this->_table)
  985. ->set($values);
  986. if (is_array($this->_primary_key))
  987. {
  988. foreach($this->_primary_key as $field)
  989. {
  990. $query->where($this->_fields[$field]->column, '=', $this->_original[$field]);
  991. }
  992. }
  993. else
  994. {
  995. $query->where($this->_fields[$this->_primary_key]->column, '=', $this->_original[$this->_primary_key]);
  996. }
  997. $query->execute($this->_db);
  998. }
  999. if ($relations)
  1000. {
  1001. foreach ($relations as $name => $value)
  1002. {
  1003. $field = $this->_fields[$name];
  1004. $model = Sprig::factory($field->model);
  1005. // Find old relationships that must be deleted
  1006. if ($old = array_diff($this->_original[$name], $value))
  1007. {
  1008. DB::delete($field->through)
  1009. ->where($this->fk(), '=', $this->{$this->_primary_key})
  1010. ->where($model->fk(), 'IN', $old)
  1011. ->execute($this->_db);
  1012. }
  1013. // Find new relationships that must be inserted
  1014. if ($new = array_diff($value, $this->_original[$name]))
  1015. {
  1016. foreach ($new as $id)
  1017. {
  1018. DB::insert($field->through, array($this->fk(), $model->fk()))
  1019. ->values(array($this->{$this->_primary_key}, $id))
  1020. ->execute($this->_db);
  1021. }
  1022. }
  1023. }
  1024. }
  1025. // Reset the original data for this record
  1026. $this->_original = $this->as_array();
  1027. // Everything has been updated
  1028. $this->_changed = array();
  1029. }
  1030. return $this;
  1031. }
  1032. /**
  1033. * Delete the current record:
  1034. *
  1035. * - If the record is loaded, it will be deleted using primary key(s).
  1036. * - If the record is not loaded, it will be deleted using all changed fields.
  1037. * - If no data has been changed, the delete will be ignored.
  1038. *
  1039. * @param object any Database_Query_Builder_Delete, NULL for none
  1040. * @return $this
  1041. */
  1042. public function delete(Database_Query_Builder_Delete $query = NULL)
  1043. {
  1044. if ( ! $query)
  1045. {
  1046. $query = DB::delete($this->_table);
  1047. }
  1048. else
  1049. {
  1050. $query->table($this->_table);
  1051. }
  1052. if ($changed = $this->changed())
  1053. {
  1054. foreach ($changed as $field => $value)
  1055. {
  1056. $query->where($this->_fields[$field]->column, '=', $value);
  1057. }
  1058. }
  1059. else
  1060. {
  1061. if (is_array($this->_primary_key))
  1062. {
  1063. foreach($this->_primary_key as $field)
  1064. {
  1065. $query->where($this->_fields[$field]->column, '=', $this->_original[$field]);
  1066. }
  1067. }
  1068. else
  1069. {
  1070. $query->where($this->_fields[$this->_primary_key]->column, '=', $this->_original[$this->_primary_key]);
  1071. }
  1072. }
  1073. if ($query->execute($this->_db))
  1074. {
  1075. $this->state('deleted');
  1076. }
  1077. return $this;
  1078. }
  1079. /**
  1080. * Check the given data is valid. Only values that have editable fields
  1081. * will be included and checked.
  1082. *
  1083. * @throws Validate_Exception when an error is found
  1084. * @param array data to check, field => value
  1085. * @return array filtered data
  1086. */
  1087. public function check(array $data = NULL)
  1088. {
  1089. if ($data === NULL)
  1090. {
  1091. // Use the current data set
  1092. $data = $this->changed();
  1093. }
  1094. $data = Validate::factory($data);
  1095. foreach ($this->_fields as $name => $field)
  1096. {
  1097. if ( ! $data->offsetExists($name))
  1098. {
  1099. // Do not add any rules for this field
  1100. continue;
  1101. }
  1102. $data->label($name, $field->label);
  1103. if ($field->filters)
  1104. {
  1105. $data->filters($name, $field->filters);
  1106. }
  1107. if ($field->rules)
  1108. {
  1109. $data->rules($name, $field->rules);
  1110. }
  1111. if ($field->callbacks)
  1112. {
  1113. $data->callbacks($name, $field->callbacks);
  1114. }
  1115. }
  1116. if ( ! $data->check())
  1117. {
  1118. throw new Validate_Exception($data);
  1119. }
  1120. return $data->as_array();
  1121. }
  1122. /**
  1123. * Callback for validating unique fields.
  1124. *
  1125. * @param object Validate array
  1126. * @param string field name
  1127. * @return void
  1128. */
  1129. public function _unique_field(Validate $array, $field)
  1130. {
  1131. if ($array[$field])
  1132. {
  1133. $query = DB::select($this->_fields[$this->_primary_key]->column)
  1134. ->from($this->_table)
  1135. ->where($this->_fields[$field]->column, '=', $array[$field])
  1136. ->execute($this->_db);
  1137. if (count($query))
  1138. {
  1139. $array->error($field, 'unique');
  1140. }
  1141. }
  1142. }
  1143. /**
  1144. * Initialize the fields. This method will only be called once
  1145. * by Sprig::init(). All models must define this method!
  1146. *
  1147. * @return void
  1148. */
  1149. abstract protected function _init();
  1150. } // End Sprig