PageRenderTime 42ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 1ms

/core/phpactiverecord/Model.php

https://github.com/rosianesrocha/nanico
PHP | 1673 lines | 909 code | 135 blank | 629 comment | 73 complexity | 3e394f179d335e297987b3b16d28519a MD5 | raw file
  1. <?php
  2. /**
  3. * @package ActiveRecord
  4. */
  5. namespace ActiveRecord;
  6. /**
  7. * The base class for your models.
  8. *
  9. * Defining an ActiveRecord model for a table called people and orders:
  10. *
  11. * <code>
  12. * CREATE TABLE people(
  13. * id int primary key auto_increment,
  14. * parent_id int,
  15. * first_name varchar(50),
  16. * last_name varchar(50)
  17. * );
  18. *
  19. * CREATE TABLE orders(
  20. * id int primary key auto_increment,
  21. * person_id int not null,
  22. * cost decimal(10,2),
  23. * total decimal(10,2)
  24. * );
  25. * </code>
  26. *
  27. * <code>
  28. * class Person extends ActiveRecord\Model {
  29. * static $belongs_to = array(
  30. * array('parent', 'foreign_key' => 'parent_id', 'class_name' => 'Person')
  31. * );
  32. *
  33. * static $has_many = array(
  34. * array('children', 'foreign_key' => 'parent_id', 'class_name' => 'Person'),
  35. * array('orders')
  36. * );
  37. *
  38. * static $validates_length_of = array(
  39. * array('first_name', 'within' => array(1,50)),
  40. * array('last_name', 'within' => array(1,50))
  41. * );
  42. * }
  43. *
  44. * class Order extends ActiveRecord\Model {
  45. * static $belongs_to = array(
  46. * array('person')
  47. * );
  48. *
  49. * static $validates_numericality_of = array(
  50. * array('cost', 'greater_than' => 0),
  51. * array('total', 'greater_than' => 0)
  52. * );
  53. *
  54. * static $before_save = array('calculate_total_with_tax');
  55. *
  56. * public function calculate_total_with_tax() {
  57. * $this->total = $this->cost * 0.045;
  58. * }
  59. * }
  60. * </code>
  61. *
  62. * For a more in-depth look at defining models, relationships, callbacks and many other things
  63. * please consult our {@link http://www.phpactiverecord.org/guides Guides}.
  64. *
  65. * @package ActiveRecord
  66. * @see BelongsTo
  67. * @see CallBack
  68. * @see HasMany
  69. * @see HasAndBelongsToMany
  70. * @see Serialization
  71. * @see Validations
  72. */
  73. class Model
  74. {
  75. /**
  76. * An instance of {@link Errors} and will be instantiated once a write method is called.
  77. *
  78. * @var Errors
  79. */
  80. public $errors;
  81. /**
  82. * Contains model values as column_name => value
  83. *
  84. * @var array
  85. */
  86. private $attributes = array();
  87. /**
  88. * Flag whether or not this model's attributes have been modified since it will either be null or an array of column_names that have been modified
  89. *
  90. * @var array
  91. */
  92. private $__dirty = null;
  93. /**
  94. * Flag that determines of this model can have a writer method invoked such as: save/update/insert/delete
  95. *
  96. * @var boolean
  97. */
  98. private $__readonly = false;
  99. /**
  100. * Array of relationship objects as model_attribute_name => relationship
  101. *
  102. * @var array
  103. */
  104. private $__relationships = array();
  105. /**
  106. * Flag that determines if a call to save() should issue an insert or an update sql statement
  107. *
  108. * @var boolean
  109. */
  110. private $__new_record = true;
  111. /**
  112. * Set to the name of the connection this {@link Model} should use.
  113. *
  114. * @var string
  115. */
  116. static $connection;
  117. /**
  118. * Set to the name of the database this Model's table is in.
  119. *
  120. * @var string
  121. */
  122. static $db;
  123. /**
  124. * Set this to explicitly specify the model's table name if different from inferred name.
  125. *
  126. * If your table doesn't follow our table name convention you can set this to the
  127. * name of your table to explicitly tell ActiveRecord what your table is called.
  128. *
  129. * @var string
  130. */
  131. static $table_name;
  132. /**
  133. * Set this to override the default primary key name if different from default name of "id".
  134. *
  135. * @var string
  136. */
  137. static $primary_key;
  138. /**
  139. * Set this to explicitly specify the sequence name for the table.
  140. *
  141. * @var string
  142. */
  143. static $sequence;
  144. /**
  145. * Allows you to create aliases for attributes.
  146. *
  147. * <code>
  148. * class Person extends ActiveRecord\Model {
  149. * static $alias_attribute = array(
  150. * 'the_first_name' => 'first_name',
  151. * 'the_last_name' => 'last_name');
  152. * }
  153. *
  154. * $person = Person::first();
  155. * $person->the_first_name = 'Tito';
  156. * echo $person->the_first_name;
  157. * </code>
  158. *
  159. * @var array
  160. */
  161. static $alias_attribute = array();
  162. /**
  163. * Whitelist of attributes that are checked from mass-assignment calls such as constructing a model or using update_attributes.
  164. *
  165. * This is the opposite of {@link attr_protected $attr_protected}.
  166. *
  167. * <code>
  168. * class Person extends ActiveRecord\Model {
  169. * static $attr_accessible = array('first_name','last_name');
  170. * }
  171. *
  172. * $person = new Person(array(
  173. * 'first_name' => 'Tito',
  174. * 'last_name' => 'the Grief',
  175. * 'id' => 11111));
  176. *
  177. * echo $person->id; # => null
  178. * </code>
  179. *
  180. * @var array
  181. */
  182. static $attr_accessible = array();
  183. /**
  184. * Blacklist of attributes that cannot be mass-assigned.
  185. *
  186. * This is the opposite of {@link attr_accessible $attr_accessible} and the format
  187. * for defining these are exactly the same.
  188. *
  189. * @var array
  190. */
  191. static $attr_protected = array();
  192. /**
  193. * Delegates calls to a relationship.
  194. *
  195. * <code>
  196. * class Person extends ActiveRecord\Model {
  197. * static $belongs_to = array(array('venue'),array('host'));
  198. * static $delegate = array(
  199. * array('name', 'state', 'to' => 'venue'),
  200. * array('name', 'to' => 'host', 'prefix' => 'woot'));
  201. * }
  202. * </code>
  203. *
  204. * Can then do:
  205. *
  206. * <code>
  207. * $person->state # same as calling $person->venue->state
  208. * $person->name # same as calling $person->venue->name
  209. * $person->woot_name # same as calling $person->host->name
  210. * </code>
  211. *
  212. * @var array
  213. */
  214. static $delegate = array();
  215. /**
  216. * Define customer setters methods for the model.
  217. *
  218. * You can also use this to define custom setters for attributes as well.
  219. *
  220. * <code>
  221. * class User extends ActiveRecord\Model {
  222. * static $setters = array('password','more','even_more');
  223. *
  224. * # now to define the setter methods. Note you must
  225. * # prepend set_ to your method name:
  226. * function set_password($plaintext) {
  227. * $this->encrypted_password = md5($plaintext);
  228. * }
  229. * }
  230. *
  231. * $user = new User();
  232. * $user->password = 'plaintext'; # will call $user->set_password('plaintext')
  233. * </code>
  234. *
  235. * If you define a custom setter with the same name as an attribute then you
  236. * will need to use assign_attribute() to assign the value to the attribute.
  237. * This is necessary due to the way __set() works.
  238. *
  239. * For example, assume 'name' is a field on the table and we're defining a
  240. * custom setter for 'name':
  241. *
  242. * <code>
  243. * class User extends ActiveRecord\Model {
  244. * static $setters = array('name');
  245. *
  246. * # INCORRECT way to do it
  247. * # function set_name($name) {
  248. * # $this->name = strtoupper($name);
  249. * # }
  250. *
  251. * function set_name($name) {
  252. * $this->assign_attribute('name',strtoupper($name));
  253. * }
  254. * }
  255. *
  256. * $user = new User();
  257. * $user->name = 'bob';
  258. * echo $user->name; # => BOB
  259. * </code>
  260. *
  261. * @var array
  262. */
  263. static $setters = array();
  264. /**
  265. * Define customer getter methods for the model.
  266. *
  267. * <code>
  268. * class User extends ActiveRecord\Model {
  269. * static $getters = array('middle_initial','more','even_more');
  270. *
  271. * # now to define the getter method. Note you must
  272. * # prepend get_ to your method name:
  273. * function get_middle_initial() {
  274. * return $this->middle_name{0};
  275. * }
  276. * }
  277. *
  278. * $user = new User();
  279. * echo $user->middle_name; # will call $user->get_middle_name()
  280. * </code>
  281. *
  282. * If you define a custom getter with the same name as an attribute then you
  283. * will need to use read_attribute() to get the attribute's value.
  284. * This is necessary due to the way __get() works.
  285. *
  286. * For example, assume 'name' is a field on the table and we're defining a
  287. * custom getter for 'name':
  288. *
  289. * <code>
  290. * class User extends ActiveRecord\Model {
  291. * static $getters = array('name');
  292. *
  293. * # INCORRECT way to do it
  294. * # function get_name() {
  295. * # return strtoupper($this->name);
  296. * # }
  297. *
  298. * function get_name() {
  299. * return strtoupper($this->read_attribute('name'));
  300. * }
  301. * }
  302. *
  303. * $user = new User();
  304. * $user->name = 'bob';
  305. * echo $user->name; # => BOB
  306. * </code>
  307. *
  308. * @var array
  309. */
  310. static $getters = array();
  311. /**
  312. * Constructs a model.
  313. *
  314. * When a user instantiates a new object (e.g.: it was not ActiveRecord that instantiated via a find)
  315. * then @var $attributes will be mapped according to the schema's defaults. Otherwise, the given
  316. * $attributes will be mapped via set_attributes_via_mass_assignment.
  317. *
  318. * <code>
  319. * new Person(array('first_name' => 'Tito', 'last_name' => 'the Grief'));
  320. * </code>
  321. *
  322. * @param array $attributes Hash containing names and values to mass assign to the model
  323. * @param boolean $guard_attributes Set to true to guard attributes
  324. * @param boolean $instantiating_via_find Set to true if this model is being created from a find call
  325. * @param boolean $new_record Set to true if this should be considered a new record
  326. * @return Model
  327. */
  328. public function __construct(array $attributes=array(), $guard_attributes=true, $instantiating_via_find=false, $new_record=true)
  329. {
  330. $this->__new_record = $new_record;
  331. // initialize attributes applying defaults
  332. if (!$instantiating_via_find)
  333. {
  334. foreach (static::table()->columns as $name => $meta)
  335. $this->attributes[$meta->inflected_name] = $meta->default;
  336. }
  337. $this->set_attributes_via_mass_assignment($attributes, $guard_attributes);
  338. // since all attribute assignment now goes thru assign_attributes() we want to reset
  339. // dirty if instantiating via find since nothing is really dirty when doing that
  340. if ($instantiating_via_find)
  341. $this->__dirty = array();
  342. $this->invoke_callback('after_construct',false);
  343. }
  344. /**
  345. * Magic method which delegates to read_attribute(). This handles firing off getter methods,
  346. * as they are not checked/invoked inside of read_attribute(). This circumvents the problem with
  347. * a getter being accessed with the same name as an actual attribute.
  348. *
  349. * @see read_attribute()
  350. * @param string $name Name of an attribute
  351. * @return mixed The value of the attribute
  352. */
  353. public function &__get($name)
  354. {
  355. // check for getter
  356. if (in_array("get_$name",static::$getters))
  357. {
  358. $name = "get_$name";
  359. $value = $this->$name();
  360. return $value;
  361. }
  362. return $this->read_attribute($name);
  363. }
  364. /**
  365. * Determines if an attribute exists for this {@link Model}.
  366. *
  367. * @param string $attribute_name
  368. * @return boolean
  369. */
  370. public function __isset($attribute_name)
  371. {
  372. return array_key_exists($attribute_name,$this->attributes) || array_key_exists($attribute_name,static::$alias_attribute);
  373. }
  374. /**
  375. * Magic allows un-defined attributes to set via $attributes
  376. *
  377. * @throws {@link UndefinedPropertyException} if $name does not exist
  378. * @param string $name Name of attribute, relationship or other to set
  379. * @param mixed $value The value
  380. * @return mixed The value
  381. */
  382. public function __set($name, $value)
  383. {
  384. if (array_key_exists($name, static::$alias_attribute))
  385. $name = static::$alias_attribute[$name];
  386. elseif (in_array("set_$name",static::$setters))
  387. {
  388. $name = "set_$name";
  389. return $this->$name($value);
  390. }
  391. if (array_key_exists($name,$this->attributes))
  392. return $this->assign_attribute($name,$value);
  393. foreach (static::$delegate as &$item)
  394. {
  395. if (($delegated_name = $this->is_delegated($name,$item)))
  396. return $this->$item['to']->$delegated_name = $value;
  397. }
  398. throw new UndefinedPropertyException(get_called_class(),$name);
  399. }
  400. public function __wakeup()
  401. {
  402. // make sure the models Table instance gets initialized when waking up
  403. static::table();
  404. }
  405. /**
  406. * Assign a value to an attribute.
  407. *
  408. * @param string $name Name of the attribute
  409. * @param mixed &$value Value of the attribute
  410. * @return mixed the attribute value
  411. */
  412. public function assign_attribute($name, $value)
  413. {
  414. $table = static::table();
  415. if (array_key_exists($name,$table->columns) && !is_object($value))
  416. $value = $table->columns[$name]->cast($value,static::connection());
  417. // convert php's \DateTime to ours
  418. if ($value instanceof \DateTime)
  419. $value = new DateTime($value->format('Y-m-d H:i:s T'));
  420. // make sure DateTime values know what model they belong to so
  421. // dirty stuff works when calling set methods on the DateTime object
  422. if ($value instanceof DateTime)
  423. $value->attribute_of($this,$name);
  424. $this->attributes[$name] = $value;
  425. $this->flag_dirty($name);
  426. return $value;
  427. }
  428. /**
  429. * Retrieves an attribute's value or a relationship object based on the name passed. If the attribute
  430. * accessed is 'id' then it will return the model's primary key no matter what the actual attribute name is
  431. * for the primary key.
  432. *
  433. * @param string $name Name of an attribute
  434. * @return mixed The value of the attribute
  435. * @throws {@link UndefinedPropertyException} if name could not be resolved to an attribute, relationship, ...
  436. */
  437. public function &read_attribute($name)
  438. {
  439. // check for aliased attribute
  440. if (array_key_exists($name, static::$alias_attribute))
  441. $name = static::$alias_attribute[$name];
  442. // check for attribute
  443. if (array_key_exists($name,$this->attributes))
  444. return $this->attributes[$name];
  445. // check relationships if no attribute
  446. if (array_key_exists($name,$this->__relationships))
  447. return $this->__relationships[$name];
  448. $table = static::table();
  449. // this may be first access to the relationship so check Table
  450. if (($relationship = $table->get_relationship($name)))
  451. {
  452. $this->__relationships[$name] = $relationship->load($this);
  453. return $this->__relationships[$name];
  454. }
  455. if ($name == 'id')
  456. {
  457. if (count($this->get_primary_key()) > 1)
  458. throw new Exception("TODO composite key support");
  459. if (isset($this->attributes[$table->pk[0]]))
  460. return $this->attributes[$table->pk[0]];
  461. }
  462. //do not remove - have to return null by reference in strict mode
  463. $null = null;
  464. foreach (static::$delegate as &$item)
  465. {
  466. if (($delegated_name = $this->is_delegated($name,$item)))
  467. {
  468. $to = $item['to'];
  469. if ($this->$to)
  470. {
  471. $val =& $this->$to->$delegated_name;
  472. return $val;
  473. }
  474. else
  475. return $null;
  476. }
  477. }
  478. throw new UndefinedPropertyException(get_called_class(),$name);
  479. }
  480. /**
  481. * Flags an attribute as dirty.
  482. *
  483. * @param string $name Attribute name
  484. */
  485. public function flag_dirty($name)
  486. {
  487. if (!$this->__dirty)
  488. $this->__dirty = array();
  489. $this->__dirty[$name] = true;
  490. }
  491. /**
  492. * Returns hash of attributes that have been modified since loading the model.
  493. *
  494. * @return mixed null if no dirty attributes otherwise returns array of dirty attributes.
  495. */
  496. public function dirty_attributes()
  497. {
  498. if (!$this->__dirty)
  499. return null;
  500. $dirty = array_intersect_key($this->attributes,$this->__dirty);
  501. return !empty($dirty) ? $dirty : null;
  502. }
  503. /**
  504. * Returns a copy of the model's attributes hash.
  505. *
  506. * @return array A copy of the model's attribute data
  507. */
  508. public function attributes()
  509. {
  510. return $this->attributes;
  511. }
  512. /**
  513. * Retrieve the primary key name.
  514. *
  515. * @return string The primary key for the model
  516. */
  517. public function get_primary_key()
  518. {
  519. return Table::load(get_class($this))->pk;
  520. }
  521. /**
  522. * Returns the actual attribute name if $name is aliased.
  523. *
  524. * @param string $name An attribute name
  525. * @return string
  526. */
  527. public function get_real_attribute_name($name)
  528. {
  529. if (array_key_exists($name,$this->attributes))
  530. return $name;
  531. if (array_key_exists($name,static::$alias_attribute))
  532. return static::$alias_attribute[$name];
  533. return null;
  534. }
  535. /**
  536. * Returns array of validator data for this Model.
  537. *
  538. * Will return an array looking like:
  539. *
  540. * <code>
  541. * array(
  542. * 'name' => array(
  543. * array('validator' => 'validates_presence_of'),
  544. * array('validator' => 'validates_inclusion_of', 'in' => array('Bob','Joe','John')),
  545. * 'password' => array(
  546. * array('validator' => 'validates_length_of', 'minimum' => 6))
  547. * )
  548. * );
  549. * </code>
  550. *
  551. * @return array An array containing validator data for this model.
  552. */
  553. public function get_validation_rules()
  554. {
  555. require_once 'Validations.php';
  556. $validator = new Validations($this);
  557. return $validator->rules();
  558. }
  559. /**
  560. * Returns an associative array containing values for all the attributes in $attributes
  561. *
  562. * @param array $attributes Array containing attribute names
  563. * @return array A hash containing $name => $value
  564. */
  565. public function get_values_for($attributes)
  566. {
  567. $ret = array();
  568. foreach ($attributes as $name)
  569. {
  570. if (array_key_exists($name,$this->attributes))
  571. $ret[$name] = $this->attributes[$name];
  572. }
  573. return $ret;
  574. }
  575. /**
  576. * Retrieves the name of the table for this Model.
  577. *
  578. * @return string
  579. */
  580. public static function table_name()
  581. {
  582. return static::table()->table;
  583. }
  584. /**
  585. * Returns the attribute name on the delegated relationship if $name is
  586. * delegated or null if not delegated.
  587. *
  588. * @param string $name Name of an attribute
  589. * @param array $delegate An array containing delegate data
  590. * @return delegated attribute name or null
  591. */
  592. private function is_delegated($name, &$delegate)
  593. {
  594. if ($delegate['prefix'] != '')
  595. $name = substr($name,strlen($delegate['prefix'])+1);
  596. if (is_array($delegate) && in_array($name,$delegate['delegate']))
  597. return $name;
  598. return null;
  599. }
  600. /**
  601. * Determine if the model is in read-only mode.
  602. *
  603. * @return boolean
  604. */
  605. public function is_readonly()
  606. {
  607. return $this->__readonly;
  608. }
  609. /**
  610. * Determine if the model is a new record.
  611. *
  612. * @return boolean
  613. */
  614. public function is_new_record()
  615. {
  616. return $this->__new_record;
  617. }
  618. /**
  619. * Throws an exception if this model is set to readonly.
  620. *
  621. * @throws ActiveRecord\ReadOnlyException
  622. * @param string $method_name Name of method that was invoked on model for exception message
  623. */
  624. private function verify_not_readonly($method_name)
  625. {
  626. if ($this->is_readonly())
  627. throw new ReadOnlyException(get_class($this), $method_name);
  628. }
  629. /**
  630. * Flag model as readonly.
  631. *
  632. * @param boolean $readonly Set to true to put the model into readonly mode
  633. */
  634. public function readonly($readonly=true)
  635. {
  636. $this->__readonly = $readonly;
  637. }
  638. /**
  639. * Retrieve the connection for this model.
  640. *
  641. * @return Connection
  642. */
  643. public static function connection()
  644. {
  645. return static::table()->conn;
  646. }
  647. /**
  648. * Returns the {@link Table} object for this model.
  649. *
  650. * Be sure to call in static scoping: static::table()
  651. *
  652. * @return Table
  653. */
  654. public static function table()
  655. {
  656. return Table::load(get_called_class());
  657. }
  658. /**
  659. * Creates a model and saves it to the database.
  660. *
  661. * @param array $attributes Array of the models attributes
  662. * @param boolean $validate True if the validators should be run
  663. * @return Model
  664. */
  665. public static function create($attributes, $validate=true)
  666. {
  667. $class_name = get_called_class();
  668. $model = new $class_name($attributes);
  669. $model->save($validate);
  670. return $model;
  671. }
  672. /**
  673. * Save the model to the database.
  674. *
  675. * This function will automatically determine if an INSERT or UPDATE needs to occur.
  676. * If a validation or a callback for this model returns false, then the model will
  677. * not be saved and this will return false.
  678. *
  679. * If saving an existing model only data that has changed will be saved.
  680. *
  681. * @param boolean $validate Set to true or false depending on if you want the validators to run or not
  682. * @return boolean True if the model was saved to the database otherwise false
  683. */
  684. public function save($validate=true)
  685. {
  686. $this->verify_not_readonly('save');
  687. return $this->is_new_record() ? $this->insert($validate) : $this->update($validate);
  688. }
  689. /**
  690. * Issue an INSERT sql statement for this model's attribute.
  691. *
  692. * @see save
  693. * @param boolean $validate Set to true or false depending on if you want the validators to run or not
  694. * @return boolean True if the model was saved to the database otherwise false
  695. */
  696. private function insert($validate=true)
  697. {
  698. $this->verify_not_readonly('insert');
  699. if (($validate && !$this->_validate() || !$this->invoke_callback('before_create',false)))
  700. return false;
  701. $table = static::table();
  702. if (!($attributes = $this->dirty_attributes()))
  703. $attributes = $this->attributes;
  704. $pk = $this->get_primary_key();
  705. $use_sequence = false;
  706. if ($table->sequence && !isset($attributes[$pk[0]]))
  707. {
  708. if (($conn = static::connection()) instanceof OciAdapter)
  709. {
  710. // terrible oracle makes us select the nextval first
  711. $attributes[$pk[0]] = $conn->get_next_sequence_value($table->sequence);
  712. $table->insert($attributes);
  713. $this->attributes[$pk[0]] = $attributes[$pk[0]];
  714. }
  715. else
  716. {
  717. // unset pk that was set to null
  718. if (array_key_exists($pk[0],$attributes))
  719. unset($attributes[$pk[0]]);
  720. $table->insert($attributes,$pk[0],$table->sequence);
  721. $use_sequence = true;
  722. }
  723. }
  724. else
  725. $table->insert($attributes);
  726. // if we've got an autoincrementing/sequenced pk set it
  727. if (count($pk) == 1)
  728. {
  729. $column = $table->get_column_by_inflected_name($pk[0]);
  730. if ($column->auto_increment || $use_sequence)
  731. $this->attributes[$pk[0]] = $table->conn->insert_id($table->sequence);
  732. }
  733. $this->invoke_callback('after_create',false);
  734. $this->__new_record = false;
  735. return true;
  736. }
  737. /**
  738. * Issue an UPDATE sql statement for this model's dirty attributes.
  739. *
  740. * @see save
  741. * @param boolean $validate Set to true or false depending on if you want the validators to run or not
  742. * @return boolean True if the model was saved to the database otherwise false
  743. */
  744. private function update($validate=true)
  745. {
  746. $this->verify_not_readonly('update');
  747. if ($validate && !$this->_validate())
  748. return false;
  749. if ($this->is_dirty())
  750. {
  751. $pk = $this->values_for_pk();
  752. if (empty($pk))
  753. throw new ActiveRecordException("Cannot update, no primary key defined for: " . get_called_class());
  754. if (!$this->invoke_callback('before_update',false))
  755. return false;
  756. $dirty = $this->dirty_attributes();
  757. static::table()->update($dirty,$pk);
  758. $this->invoke_callback('after_update',false);
  759. }
  760. return true;
  761. }
  762. /**
  763. * Deletes this model from the database and returns true if successful.
  764. *
  765. * @return boolean
  766. */
  767. public function delete()
  768. {
  769. $this->verify_not_readonly('delete');
  770. $pk = $this->values_for_pk();
  771. if (empty($pk))
  772. throw new ActiveRecordException("Cannot delete, no primary key defined for: " . get_called_class());
  773. if (!$this->invoke_callback('before_destroy',false))
  774. return false;
  775. static::table()->delete($pk);
  776. $this->invoke_callback('after_destroy',false);
  777. return true;
  778. }
  779. /**
  780. * Helper that creates an array of values for the primary key(s).
  781. *
  782. * @return array An array in the form array(key_name => value, ...)
  783. */
  784. public function values_for_pk()
  785. {
  786. return $this->values_for(static::table()->pk);
  787. }
  788. /**
  789. * Helper to return a hash of values for the specified attributes.
  790. *
  791. * @param array $attribute_names Array of attribute names
  792. * @return array An array in the form array(name => value, ...)
  793. */
  794. public function values_for($attribute_names)
  795. {
  796. $filter = array();
  797. foreach ($attribute_names as $name)
  798. $filter[$name] = $this->$name;
  799. return $filter;
  800. }
  801. /**
  802. * Validates the model.
  803. *
  804. * @return boolean True if passed validators otherwise false
  805. */
  806. private function _validate()
  807. {
  808. require_once 'Validations.php';
  809. $validator = new Validations($this);
  810. $validation_on = 'validation_on_' . ($this->is_new_record() ? 'create' : 'update');
  811. foreach (array('before_validation', "before_$validation_on") as $callback)
  812. {
  813. if (!$this->invoke_callback($callback,false))
  814. return false;
  815. }
  816. $this->errors = $validator->validate();
  817. foreach (array('after_validation', "after_$validation_on") as $callback)
  818. $this->invoke_callback($callback,false);
  819. if (!$this->errors->is_empty())
  820. return false;
  821. return true;
  822. }
  823. /**
  824. * Returns true if the model has been modified.
  825. *
  826. * @return boolean true if modified
  827. */
  828. public function is_dirty()
  829. {
  830. return empty($this->__dirty) ? false : true;
  831. }
  832. /**
  833. * Run validations on model and returns whether or not model passed validation.
  834. *
  835. * @see is_invalid
  836. * @return boolean
  837. */
  838. public function is_valid()
  839. {
  840. return $this->_validate();
  841. }
  842. /**
  843. * Runs validations and returns true if invalid.
  844. *
  845. * @see is_valid
  846. * @return boolean
  847. */
  848. public function is_invalid()
  849. {
  850. return !$this->_validate();
  851. }
  852. /**
  853. * Updates a model's timestamps.
  854. */
  855. public function set_timestamps()
  856. {
  857. $now = date('Y-m-d H:i:s');
  858. if (isset($this->updated_at))
  859. $this->updated_at = $now;
  860. if (isset($this->created_at) && $this->is_new_record())
  861. $this->created_at = $now;
  862. }
  863. /**
  864. * Mass update the model with an array of attribute data and saves to the database.
  865. *
  866. * @param array $attributes An attribute data array in the form array(name => value, ...)
  867. * @return boolean True if successfully updated and saved otherwise false
  868. */
  869. public function update_attributes($attributes)
  870. {
  871. $this->set_attributes($attributes);
  872. return $this->save();
  873. }
  874. /**
  875. * Updates a single attribute and saves the record without going through the normal validation procedure.
  876. *
  877. * @param string $name Name of attribute
  878. * @param mixed $value Value of the attribute
  879. * @return boolean True if successful otherwise false
  880. */
  881. public function update_attribute($name, $value)
  882. {
  883. $this->__set($name, $value);
  884. return $this->update(false);
  885. }
  886. /**
  887. * Mass update the model with data from an attributes hash.
  888. *
  889. * Unlike update_attributes() this method only updates the model's data
  890. * but DOES NOT save it to the database.
  891. *
  892. * @see update_attributes
  893. * @param array $attributes An array containing data to update in the form array(name => value, ...)
  894. */
  895. public function set_attributes(array $attributes)
  896. {
  897. $this->set_attributes_via_mass_assignment($attributes, true);
  898. }
  899. /**
  900. * Passing $guard_attributes as true will throw an exception if an attribute does not exist.
  901. *
  902. * @throws ActiveRecord\UndefinedPropertyException
  903. * @param array $attributes An array in the form array(name => value, ...)
  904. * @param boolean $guard_attributes Flag of whether or not attributes should be guarded
  905. */
  906. private function set_attributes_via_mass_assignment(array &$attributes, $guard_attributes)
  907. {
  908. //access uninflected columns since that is what we would have in result set
  909. $table = static::table();
  910. $exceptions = array();
  911. $use_attr_accessible = !empty(static::$attr_accessible);
  912. $use_attr_protected = !empty(static::$attr_protected);
  913. $connection = static::connection();
  914. foreach ($attributes as $name => $value)
  915. {
  916. // is a normal field on the table
  917. if (array_key_exists($name,$table->columns))
  918. {
  919. $value = $table->columns[$name]->cast($value,$connection);
  920. $name = $table->columns[$name]->inflected_name;
  921. }
  922. if ($guard_attributes)
  923. {
  924. if ($use_attr_accessible && !in_array($name,static::$attr_accessible))
  925. continue;
  926. if ($use_attr_protected && in_array($name,static::$attr_protected))
  927. continue;
  928. // set valid table data
  929. try {
  930. $this->$name = $value;
  931. } catch (UndefinedPropertyException $e) {
  932. $exceptions[] = $e->getMessage();
  933. }
  934. }
  935. else
  936. {
  937. // ignore OciAdapter's limit() stuff
  938. if ($name == 'ar_rnum__')
  939. continue;
  940. // set arbitrary data
  941. $this->assign_attribute($name,$value);
  942. }
  943. }
  944. if (!empty($exceptions))
  945. throw new UndefinedPropertyException(get_called_class(),$exceptions);
  946. }
  947. /**
  948. * Add a model to the given named ($name) relationship.
  949. *
  950. * @internal This should <strong>only</strong> be used by eager load
  951. * @param Model $model
  952. * @param $name of relationship for this table
  953. * @return void
  954. */
  955. public function set_relationship_from_eager_load(Model $model=null, $name)
  956. {
  957. $table = static::table();
  958. if (($rel = $table->get_relationship($name)))
  959. {
  960. if ($rel->is_poly())
  961. {
  962. // if the related model is null and it is a poly then we should have an empty array
  963. if (is_null($model))
  964. return $this->__relationships[$name] = array();
  965. else
  966. return $this->__relationships[$name][] = $model;
  967. }
  968. else
  969. return $this->__relationships[$name] = $model;
  970. }
  971. throw new RelationshipException("Relationship named $name has not been declared for class: {$table->class->getName()}");
  972. }
  973. /**
  974. * Reloads the attributes and relationships of this object from the database.
  975. *
  976. * @return Model
  977. */
  978. public function reload()
  979. {
  980. $this->__relationships = array();
  981. $pk = array_values($this->get_values_for($this->get_primary_key()));
  982. $this->set_attributes($this->find($pk)->attributes);
  983. $this->reset_dirty();
  984. return $this;
  985. }
  986. public function __clone()
  987. {
  988. $this->__relationships = array();
  989. $this->reset_dirty();
  990. return $this;
  991. }
  992. /**
  993. * Resets the dirty array.
  994. *
  995. * @see dirty_attributes
  996. */
  997. public function reset_dirty()
  998. {
  999. $this->__dirty = null;
  1000. }
  1001. /**
  1002. * A list of valid finder options.
  1003. *
  1004. * @var array
  1005. */
  1006. static $VALID_OPTIONS = array('conditions', 'limit', 'offset', 'order', 'select', 'joins', 'include', 'readonly', 'group', 'from', 'having');
  1007. /**
  1008. * Enables the use of dynamic finders.
  1009. *
  1010. * Dynamic finders are just an easy way to do queries quickly without having to
  1011. * specify an options array with conditions in it.
  1012. *
  1013. * <code>
  1014. * SomeModel::find_by_first_name('Tito');
  1015. * SomeModel::find_by_first_name_and_last_name('Tito','the Grief');
  1016. * SomeModel::find_by_first_name_or_last_name('Tito','the Grief');
  1017. * SomeModel::find_all_by_last_name('Smith');
  1018. * SomeModel::count_by_name('Bob')
  1019. * SomeModel::count_by_name_or_state('Bob','VA')
  1020. * SomeModel::count_by_name_and_state('Bob','VA')
  1021. * </code>
  1022. *
  1023. * You can also create the model if the find call returned no results:
  1024. *
  1025. * <code>
  1026. * Person::find_or_create_by_name('Tito');
  1027. *
  1028. * # would be the equivalent of
  1029. * if (!Person::find_by_name('Tito'))
  1030. * Person::create(array('Tito'));
  1031. * </code>
  1032. *
  1033. * Some other examples of find_or_create_by:
  1034. *
  1035. * <code>
  1036. * Person::find_or_create_by_name_and_id('Tito',1);
  1037. * Person::find_or_create_by_name_and_id(array('name' => 'Tito', 'id' => 1));
  1038. * </code>
  1039. *
  1040. * @param string $method Name of method
  1041. * @param mixed $args Method args
  1042. * @return Model
  1043. * @throws {@link ActiveRecordException} if invalid query
  1044. * @see find
  1045. */
  1046. public static function __callStatic($method, $args)
  1047. {
  1048. $options = static::extract_and_validate_options($args);
  1049. $create = false;
  1050. if (substr($method,0,17) == 'find_or_create_by')
  1051. {
  1052. $attributes = substr($method,17);
  1053. // can't take any finders with OR in it when doing a find_or_create_by
  1054. if (strpos($attributes,'_or_') !== false)
  1055. throw new ActiveRecordException("Cannot use OR'd attributes in find_or_create_by");
  1056. $create = true;
  1057. $method = 'find_by' . substr($method,17);
  1058. }
  1059. if (substr($method,0,7) === 'find_by')
  1060. {
  1061. $attributes = substr($method,8);
  1062. $options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::table()->conn,$attributes,$args,static::$alias_attribute);
  1063. if (!($ret = static::find('first',$options)) && $create)
  1064. return static::create(SQLBuilder::create_hash_from_underscored_string($attributes,$args,static::$alias_attribute));
  1065. return $ret;
  1066. }
  1067. elseif (substr($method,0,11) === 'find_all_by')
  1068. {
  1069. $options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::table()->conn,substr($method,12),$args,static::$alias_attribute);
  1070. return static::find('all',$options);
  1071. }
  1072. elseif (substr($method,0,8) === 'count_by')
  1073. {
  1074. $options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::table()->conn,substr($method,9),$args,static::$alias_attribute);
  1075. return static::count($options);
  1076. }
  1077. throw new ActiveRecordException("Call to undefined method: $method");
  1078. }
  1079. /**
  1080. * Enables the use of build|create for associations.
  1081. *
  1082. * @param string $method Name of method
  1083. * @param mixed $args Method args
  1084. * @return mixed An instance of a given {@link AbstractRelationship}
  1085. */
  1086. public function __call($method, $args)
  1087. {
  1088. //check for build|create_association methods
  1089. if (preg_match('/(build|create)_/', $method))
  1090. {
  1091. if (!empty($args))
  1092. $args = $args[0];
  1093. $association_name = str_replace(array('build_', 'create_'), '', $method);
  1094. if (($association = static::table()->get_relationship($association_name)))
  1095. {
  1096. //access association to ensure that the relationship has been loaded
  1097. //so that we do not double-up on records if we append a newly created
  1098. $this->$association_name;
  1099. $method = str_replace($association_name,'association', $method);
  1100. return $association->$method($this, $args);
  1101. }
  1102. }
  1103. throw new ActiveRecordException("Call to undefined method: $method");
  1104. }
  1105. /**
  1106. * Alias for self::find('all').
  1107. *
  1108. * @see find
  1109. * @return array array of records found
  1110. */
  1111. public static function all(/* ... */)
  1112. {
  1113. return call_user_func_array('static::find',array_merge(array('all'),func_get_args()));
  1114. }
  1115. /**
  1116. * Get a count of qualifying records.
  1117. *
  1118. * <code>
  1119. * YourModel::count(array('conditions' => 'amount > 3.14159265'));
  1120. * </code>
  1121. *
  1122. * @see find
  1123. * @return int Number of records that matched the query
  1124. */
  1125. public static function count(/* ... */)
  1126. {
  1127. $args = func_get_args();
  1128. $options = static::extract_and_validate_options($args);
  1129. $options['select'] = 'COUNT(*)';
  1130. if (!empty($args))
  1131. {
  1132. if (is_hash($args[0]))
  1133. $options['conditions'] = $args[0];
  1134. else
  1135. $options['conditions'] = call_user_func_array('static::pk_conditions',$args);
  1136. }
  1137. $table = static::table();
  1138. $sql = $table->options_to_sql($options);
  1139. $values = $sql->get_where_values();
  1140. return $table->conn->query_and_fetch_one($sql->to_s(),$values);
  1141. }
  1142. /**
  1143. * Determine if a record exists.
  1144. *
  1145. * <code>
  1146. * SomeModel::exists(123);
  1147. * SomeModel::exists(array('conditions' => array('id=? and name=?', 123, 'Tito')));
  1148. * SomeModel::exists(array('id' => 123, 'name' => 'Tito'));
  1149. * </code>
  1150. *
  1151. * @see find
  1152. * @return boolean
  1153. */
  1154. public static function exists(/* ... */)
  1155. {
  1156. return call_user_func_array('static::count',func_get_args()) > 0 ? true : false;
  1157. }
  1158. /**
  1159. * Alias for self::find('first').
  1160. *
  1161. * @see find
  1162. * @return Model The first matched record or null if not found
  1163. */
  1164. public static function first(/* ... */)
  1165. {
  1166. return call_user_func_array('static::find',array_merge(array('first'),func_get_args()));
  1167. }
  1168. /**
  1169. * Alias for self::find('last')
  1170. *
  1171. * @see find
  1172. * @return Model The last matched record or null if not found
  1173. */
  1174. public static function last(/* ... */)
  1175. {
  1176. return call_user_func_array('static::find',array_merge(array('last'),func_get_args()));
  1177. }
  1178. /**
  1179. * Find records in the database.
  1180. *
  1181. * Finding by the primary key:
  1182. *
  1183. * <code>
  1184. * # queries for the model with id=123
  1185. * YourModel::find(123);
  1186. *
  1187. * # queries for model with id in(1,2,3)
  1188. * YourModel::find(1,2,3);
  1189. *
  1190. * # finding by pk accepts an options array
  1191. * YourModel::find(123,array('order' => 'name desc'));
  1192. * </code>
  1193. *
  1194. * Finding by using a conditions array:
  1195. *
  1196. * <code>
  1197. * YourModel::find('first', array('conditions' => array('name=?','Tito'),
  1198. * 'order' => 'name asc'))
  1199. * YourModel::find('all', array('conditions' => 'amount > 3.14159265'));
  1200. * YourModel::find('all', array('conditions' => array('id in(?)', array(1,2,3))));
  1201. * </code>
  1202. *
  1203. * Finding by using a hash:
  1204. *
  1205. * <code>
  1206. * YourModel::find(array('name' => 'Tito', 'id' => 1));
  1207. * YourModel::find('first',array('name' => 'Tito', 'id' => 1));
  1208. * YourModel::find('all',array('name' => 'Tito', 'id' => 1));
  1209. * </code>
  1210. *
  1211. * An options array can take the following parameters:
  1212. *
  1213. * <ul>
  1214. * <li><b>select:</b> A SQL fragment for what fields to return such as: '*', 'people.*', 'first_name, last_name, id'</li>
  1215. * <li><b>joins:</b> A SQL join fragment such as: 'JOIN roles ON(roles.user_id=user.id)' or a named association on the model</li>
  1216. * <li><b>include:</b> TODO not implemented yet</li>
  1217. * <li><b>conditions:</b> A SQL fragment such as: 'id=1', array('id=1'), array('name=? and id=?','Tito',1), array('name IN(?)', array('Tito','Bob')),
  1218. * array('name' => 'Tito', 'id' => 1)</li>
  1219. * <li><b>limit:</b> Number of records to limit the query to</li>
  1220. * <li><b>offset:</b> The row offset to return results from for the query</li>
  1221. * <li><b>order:</b> A SQL fragment for order such as: 'name asc', 'name asc, id desc'</li>
  1222. * <li><b>readonly:</b> Return all the models in readonly mode</li>
  1223. * <li><b>group:</b> A SQL group by fragment</li>
  1224. * </ul>
  1225. *
  1226. * @throws {@link RecordNotFound} if no options are passed or finding by pk and no records matched
  1227. * @return mixed An array of records found if doing a find_all otherwise a
  1228. * single Model object or null if it wasn't found. NULL is only return when
  1229. * doing a first/last find. If doing an all find and no records matched this
  1230. * will return an empty array.
  1231. */
  1232. public static function find(/* $type, $options */)
  1233. {
  1234. $class = get_called_class();
  1235. if (func_num_args() <= 0)
  1236. throw new RecordNotFound("Couldn't find $class without an ID");
  1237. $args = func_get_args();
  1238. $options = static::extract_and_validate_options($args);
  1239. $num_args = count($args);
  1240. $single = true;
  1241. if ($num_args > 0 && ($args[0] === 'all' || $args[0] === 'first' || $args[0] === 'last'))
  1242. {
  1243. switch ($args[0])
  1244. {
  1245. case 'all':
  1246. $single = false;
  1247. break;
  1248. case 'last':
  1249. if (!array_key_exists('order',$options))
  1250. $options['order'] = join(' DESC, ',static::table()->pk) . ' DESC';
  1251. else
  1252. $options['order'] = SQLBuilder::reverse_order($options['order']);
  1253. // fall thru
  1254. case 'first':
  1255. $options['limit'] = 1;
  1256. $options['offset'] = 0;
  1257. break;
  1258. }
  1259. $args = array_slice($args,1);
  1260. $num_args--;
  1261. }
  1262. //find by pk
  1263. elseif (1 === count($args) && 1 == $num_args)
  1264. $args = $args[0];
  1265. // anything left in $args is a find by pk
  1266. if ($num_args > 0 && !isset($options['conditions']))
  1267. return static::find_by_pk($args, $options);
  1268. $options['mapped_names'] = static::$alias_attribute;
  1269. $list = static::table()->find($options);
  1270. return $single ? (!empty($list) ? $list[0] : null) : $list;
  1271. }
  1272. /**
  1273. * Finder method which will find by a single or array of primary keys for this model.
  1274. *
  1275. * @see find
  1276. * @param array $values An array containing values for the pk
  1277. * @param array $options An options array
  1278. * @return Model
  1279. * @throws {@link RecordNotFound} if a record could not be found
  1280. */
  1281. public static function find_by_pk($values, $options)
  1282. {
  1283. $options['conditions'] = static::pk_conditions($values);
  1284. $list = static::table()->find($options);
  1285. $results = count($list);
  1286. if ($results != ($expected = count($values)))
  1287. {
  1288. $class = get_called_class();
  1289. if ($expected == 1)
  1290. {
  1291. if (!is_array($values))
  1292. $values = array($values);
  1293. throw new RecordNotFound("Couldn't find $class with ID=" . join(',',$values));
  1294. }
  1295. $values = join(',',$values);
  1296. throw new RecordNotFound("Couldn't find all $class with IDs ($values) (found $results, but was looking for $expected)");
  1297. }
  1298. return $expected == 1 ? $list[0] : $list;
  1299. }
  1300. /**
  1301. * Find using a raw SELECT query.
  1302. *
  1303. * <code>
  1304. * YourModel::find_by_sql("SELECT * FROM people WHERE name=?",array('Tito'));
  1305. * YourModel::find_by_sql("SELECT * FROM people WHERE name='Tito'");
  1306. * </code>
  1307. *
  1308. * @param string $sql The raw SELECT query
  1309. * @param array $values An array of values for any parameters that needs to be bound
  1310. * @return array An array of models
  1311. */
  1312. public static function find_by_sql($sql, $values=null)
  1313. {
  1314. return static::table()->find_by_sql($sql, $values, true);
  1315. }
  1316. /**
  1317. * Determines if the specified array is a valid ActiveRecord options array.
  1318. *
  1319. * @param array $array An options array
  1320. * @param bool $throw True to throw an exception if not valid
  1321. * @return boolean True if valid otherwise valse
  1322. * @throws {@link ActiveRecordException} if the array contained any invalid options
  1323. */
  1324. public static function is_options_hash($array, $throw=true)
  1325. {
  1326. if (is_hash($array))
  1327. {
  1328. $keys = array_keys($array);
  1329. $diff = array_diff($keys,self::$VALID_OPTIONS);
  1330. if (!empty($diff) && $throw)
  1331. throw new ActiveRecordException("Unknown key(s): " . join(', ',$diff));
  1332. $intersect = array_intersect($keys,self::$VALID_OPTIONS);
  1333. if (!empty($intersect))
  1334. return true;
  1335. }
  1336. return false;
  1337. }
  1338. /**
  1339. * Returns a hash containing the names => values of the primary key.
  1340. *
  1341. * @internal This needs to eventually support composite keys.
  1342. * @param mixed $args Primary key value(s)
  1343. * @return array An array in the form array(name => value, ...)
  1344. */
  1345. public static function pk_conditions($args)
  1346. {
  1347. $table = static::table();
  1348. $ret = array($table->pk[0] => $args);
  1349. return $ret;
  1350. }
  1351. /**
  1352. * Pulls out the options hash from $array if any.
  1353. *
  1354. * @internal DO NOT remove the reference on $array.
  1355. * @param array &$array An array
  1356. * @return array A valid options array
  1357. */
  1358. public static function extract_and_validate_options(array &$array)
  1359. {
  1360. $options = array();
  1361. if ($array)
  1362. {
  1363. $last = &$array[count($array)-1];
  1364. try
  1365. {
  1366. if (self::is_options_hash($last))
  1367. {
  1368. array_pop($array);
  1369. $options = $last;
  1370. }
  1371. }
  1372. catch (ActiveRecordException $e)
  1373. {
  1374. if (!is_hash($last))
  1375. throw $e;
  1376. $options = array('conditions' => $last);
  1377. }
  1378. }
  1379. return $options;
  1380. }
  1381. /**
  1382. * Returns a JSON representation of this model.
  1383. *
  1384. * @see Serialization
  1385. * @param array $options An array containing options for json serialization (see {@link Serialization} for valid options)
  1386. * @return string JSON representation of the model
  1387. */
  1388. public function to_json(array $options=array())
  1389. {
  1390. return $this->serialize('Json', $options);
  1391. }
  1392. /**
  1393. * Returns an XML representation of this model.
  1394. *
  1395. * @see Serialization
  1396. * @param array $options An array containing options for xml serialization (see {@link Serialization} for valid options)
  1397. * @return string XML representation of the model
  1398. */
  1399. public function to_xml(array $options=array())
  1400. {
  1401. return $this->serialize('Xml', $options);
  1402. }
  1403. /**
  1404. * Creates a serializer based on pre-defined to_serializer()
  1405. *
  1406. * An options array can take the following parameters:
  1407. *
  1408. * <ul>
  1409. * <li><b>only:</b> a string or array of attributes to be included.</li>
  1410. * <li><b>excluded:</b> a string or array of attributes to be excluded.</li>
  1411. * <li><b>methods:</b> a string or array of methods to invoke. The method's name will be used as a key for the final attributes array
  1412. * along with the method's returned value</li>
  1413. * <li><b>include:</b> a string or array of associated models to include in the final serialized product.</li>
  1414. * </ul>
  1415. *
  1416. * @param string $type Either Xml or Json
  1417. * @param array $options Options array for the serializer
  1418. * @return string Serialized representation of the model
  1419. */
  1420. private function serialize($type, $options)
  1421. {
  1422. require_once 'Serialization.php';
  1423. $class = "ActiveRecord\\{$type}Serializer";
  1424. $serializer = new $class($this, $options);
  1425. return $serializer->to_s();
  1426. }
  1427. /**
  1428. * Invokes the specified callback on this model.
  1429. *
  1430. * @param string $method_name Name of the call back to run.
  1431. * @param boolean $must_exist Set to true to raise an exception if the callback does not exist.
  1432. * @return boolean True if invoked or null if not
  1433. */
  1434. private function invoke_callback($method_name, $must_exist=true)
  1435. {
  1436. return static::table()->callback->invoke($this,$method_name,$must_exist);
  1437. }
  1438. /**
  1439. * Executes a block of code inside a database transaction.
  1440. *
  1441. * <code>
  1442. * YourModel::transaction(function()
  1443. * {
  1444. * YourModel::create(array("name" => "blah"));
  1445. * });
  1446. * </code>
  1447. *
  1448. * If an exception is thrown inside the closure the transaction will
  1449. * automatically be rolled back. You can also return false from your
  1450. * closure to cause a rollback:
  1451. *
  1452. * <code>
  1453. * YourModel::transaction(function()
  1454. * {
  1455. * YourModel::create(array("name" => "blah"));
  1456. * throw new Exception("rollback!");
  1457. * });
  1458. *
  1459. * YourModel::transaction(function()
  1460. * {
  1461. * YourModel::create(array("name" => "blah"));
  1462. * return false; # rollback!
  1463. * });
  1464. * </code>
  1465. *
  1466. * @param Closure $closure The closure to execute. To cause a rollback have your closure return false or throw an exception.
  1467. * @return boolean True if the transaction was committed, False if rolled back.
  1468. */
  1469. public static function transaction($closure)
  1470. {
  1471. $connection = static::connection();
  1472. try
  1473. {
  1474. $connection->transaction();
  1475. if ($closure() === false)
  1476. {
  1477. $connection->rollback();
  1478. return false;
  1479. }
  1480. else
  1481. $connection->commit();
  1482. }
  1483. catch (\Exception $e)
  1484. {
  1485. $connection->rollback();
  1486. throw $e;
  1487. }
  1488. return true;
  1489. }
  1490. };
  1491. ?>