Model.php 49 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861
  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. * 'alias_first_name' => 'first_name',
  151. * 'alias_last_name' => 'last_name');
  152. * }
  153. *
  154. * $person = Person::first();
  155. * $person->alias_first_name = 'Tito';
  156. * echo $person->alias_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. * If the attribute is both accessible and protected, it is treated as protected.
  190. *
  191. * @var array
  192. */
  193. static $attr_protected = array();
  194. /**
  195. * Delegates calls to a relationship.
  196. *
  197. * <code>
  198. * class Person extends ActiveRecord\Model {
  199. * static $belongs_to = array(array('venue'),array('host'));
  200. * static $delegate = array(
  201. * array('name', 'state', 'to' => 'venue'),
  202. * array('name', 'to' => 'host', 'prefix' => 'woot'));
  203. * }
  204. * </code>
  205. *
  206. * Can then do:
  207. *
  208. * <code>
  209. * $person->state # same as calling $person->venue->state
  210. * $person->name # same as calling $person->venue->name
  211. * $person->woot_name # same as calling $person->host->name
  212. * </code>
  213. *
  214. * @var array
  215. */
  216. static $delegate = array();
  217. /**
  218. * Constructs a model.
  219. *
  220. * When a user instantiates a new object (e.g.: it was not ActiveRecord that instantiated via a find)
  221. * then @var $attributes will be mapped according to the schema's defaults. Otherwise, the given
  222. * $attributes will be mapped via set_attributes_via_mass_assignment.
  223. *
  224. * <code>
  225. * new Person(array('first_name' => 'Tito', 'last_name' => 'the Grief'));
  226. * </code>
  227. *
  228. * @param array $attributes Hash containing names and values to mass assign to the model
  229. * @param boolean $guard_attributes Set to true to guard protected/non-accessible attributes
  230. * @param boolean $instantiating_via_find Set to true if this model is being created from a find call
  231. * @param boolean $new_record Set to true if this should be considered a new record
  232. * @return Model
  233. */
  234. public function __construct(array $attributes=array(), $guard_attributes=true, $instantiating_via_find=false, $new_record=true)
  235. {
  236. $this->__new_record = $new_record;
  237. // initialize attributes applying defaults
  238. if (!$instantiating_via_find)
  239. {
  240. foreach (static::table()->columns as $name => $meta)
  241. $this->attributes[$meta->inflected_name] = $meta->default;
  242. }
  243. $this->set_attributes_via_mass_assignment($attributes, $guard_attributes);
  244. // since all attribute assignment now goes thru assign_attributes() we want to reset
  245. // dirty if instantiating via find since nothing is really dirty when doing that
  246. if ($instantiating_via_find)
  247. $this->__dirty = array();
  248. $this->invoke_callback('after_construct',false);
  249. }
  250. /**
  251. * Magic method which delegates to read_attribute(). This handles firing off getter methods,
  252. * as they are not checked/invoked inside of read_attribute(). This circumvents the problem with
  253. * a getter being accessed with the same name as an actual attribute.
  254. *
  255. * You can also define customer getter methods for the model.
  256. *
  257. * EXAMPLE:
  258. * <code>
  259. * class User extends ActiveRecord\Model {
  260. *
  261. * # define custom getter methods. Note you must
  262. * # prepend get_ to your method name:
  263. * function get_middle_initial() {
  264. * return $this->middle_name{0};
  265. * }
  266. * }
  267. *
  268. * $user = new User();
  269. * echo $user->middle_name; # will call $user->get_middle_name()
  270. * </code>
  271. *
  272. * If you define a custom getter with the same name as an attribute then you
  273. * will need to use read_attribute() to get the attribute's value.
  274. * This is necessary due to the way __get() works.
  275. *
  276. * For example, assume 'name' is a field on the table and we're defining a
  277. * custom getter for 'name':
  278. *
  279. * <code>
  280. * class User extends ActiveRecord\Model {
  281. *
  282. * # INCORRECT way to do it
  283. * # function get_name() {
  284. * # return strtoupper($this->name);
  285. * # }
  286. *
  287. * function get_name() {
  288. * return strtoupper($this->read_attribute('name'));
  289. * }
  290. * }
  291. *
  292. * $user = new User();
  293. * $user->name = 'bob';
  294. * echo $user->name; # => BOB
  295. * </code>
  296. *
  297. *
  298. * @see read_attribute()
  299. * @param string $name Name of an attribute
  300. * @return mixed The value of the attribute
  301. */
  302. public function &__get($name)
  303. {
  304. // check for getter
  305. if (method_exists($this, "get_$name"))
  306. {
  307. $name = "get_$name";
  308. $value = $this->$name();
  309. return $value;
  310. }
  311. return $this->read_attribute($name);
  312. }
  313. /**
  314. * Determines if an attribute exists for this {@link Model}.
  315. *
  316. * @param string $attribute_name
  317. * @return boolean
  318. */
  319. public function __isset($attribute_name)
  320. {
  321. return array_key_exists($attribute_name,$this->attributes) || array_key_exists($attribute_name,static::$alias_attribute);
  322. }
  323. /**
  324. * Magic allows un-defined attributes to set via $attributes.
  325. *
  326. * You can also define customer setter methods for the model.
  327. *
  328. * EXAMPLE:
  329. * <code>
  330. * class User extends ActiveRecord\Model {
  331. *
  332. * # define custom setter methods. Note you must
  333. * # prepend set_ to your method name:
  334. * function set_password($plaintext) {
  335. * $this->encrypted_password = md5($plaintext);
  336. * }
  337. * }
  338. *
  339. * $user = new User();
  340. * $user->password = 'plaintext'; # will call $user->set_password('plaintext')
  341. * </code>
  342. *
  343. * If you define a custom setter with the same name as an attribute then you
  344. * will need to use assign_attribute() to assign the value to the attribute.
  345. * This is necessary due to the way __set() works.
  346. *
  347. * For example, assume 'name' is a field on the table and we're defining a
  348. * custom setter for 'name':
  349. *
  350. * <code>
  351. * class User extends ActiveRecord\Model {
  352. *
  353. * # INCORRECT way to do it
  354. * # function set_name($name) {
  355. * # $this->name = strtoupper($name);
  356. * # }
  357. *
  358. * function set_name($name) {
  359. * $this->assign_attribute('name',strtoupper($name));
  360. * }
  361. * }
  362. *
  363. * $user = new User();
  364. * $user->name = 'bob';
  365. * echo $user->name; # => BOB
  366. * </code>
  367. *
  368. * @throws {@link UndefinedPropertyException} if $name does not exist
  369. * @param string $name Name of attribute, relationship or other to set
  370. * @param mixed $value The value
  371. * @return mixed The value
  372. */
  373. public function __set($name, $value)
  374. {
  375. if (array_key_exists($name, static::$alias_attribute))
  376. $name = static::$alias_attribute[$name];
  377. elseif (method_exists($this,"set_$name"))
  378. {
  379. $name = "set_$name";
  380. return $this->$name($value);
  381. }
  382. if (array_key_exists($name,$this->attributes))
  383. return $this->assign_attribute($name,$value);
  384. if ($name == 'id')
  385. return $this->assign_attribute($this->get_primary_key(true),$value);
  386. foreach (static::$delegate as &$item)
  387. {
  388. if (($delegated_name = $this->is_delegated($name,$item)))
  389. return $this->$item['to']->$delegated_name = $value;
  390. }
  391. throw new UndefinedPropertyException(get_called_class(),$name);
  392. }
  393. public function __wakeup()
  394. {
  395. // make sure the models Table instance gets initialized when waking up
  396. static::table();
  397. }
  398. /**
  399. * Assign a value to an attribute.
  400. *
  401. * @param string $name Name of the attribute
  402. * @param mixed &$value Value of the attribute
  403. * @return mixed the attribute value
  404. */
  405. public function assign_attribute($name, $value)
  406. {
  407. $table = static::table();
  408. if (!is_object($value)) {
  409. if (array_key_exists($name, $table->columns)) {
  410. $value = $table->columns[$name]->cast($value, static::connection());
  411. } else {
  412. $col = $table->get_column_by_inflected_name($name);
  413. if (!is_null($col)){
  414. $value = $col->cast($value, static::connection());
  415. }
  416. }
  417. }
  418. // convert php's \DateTime to ours
  419. if ($value instanceof \DateTime)
  420. $value = new DateTime($value->format('Y-m-d H:i:s T'));
  421. // make sure DateTime values know what model they belong to so
  422. // dirty stuff works when calling set methods on the DateTime object
  423. if ($value instanceof DateTime)
  424. $value->attribute_of($this,$name);
  425. $this->attributes[$name] = $value;
  426. $this->flag_dirty($name);
  427. return $value;
  428. }
  429. /**
  430. * Retrieves an attribute's value or a relationship object based on the name passed. If the attribute
  431. * accessed is 'id' then it will return the model's primary key no matter what the actual attribute name is
  432. * for the primary key.
  433. *
  434. * @param string $name Name of an attribute
  435. * @return mixed The value of the attribute
  436. * @throws {@link UndefinedPropertyException} if name could not be resolved to an attribute, relationship, ...
  437. */
  438. public function &read_attribute($name)
  439. {
  440. // check for aliased attribute
  441. if (array_key_exists($name, static::$alias_attribute))
  442. $name = static::$alias_attribute[$name];
  443. // check for attribute
  444. if (array_key_exists($name,$this->attributes))
  445. return $this->attributes[$name];
  446. // check relationships if no attribute
  447. if (array_key_exists($name,$this->__relationships))
  448. return $this->__relationships[$name];
  449. $table = static::table();
  450. // this may be first access to the relationship so check Table
  451. if (($relationship = $table->get_relationship($name)))
  452. {
  453. $this->__relationships[$name] = $relationship->load($this);
  454. return $this->__relationships[$name];
  455. }
  456. if ($name == 'id')
  457. {
  458. $pk = $this->get_primary_key(true);
  459. if (isset($this->attributes[$pk]))
  460. return $this->attributes[$pk];
  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->__get($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. * Check if a particular attribute has been modified since loading the model.
  505. * @param string $attribute Name of the attribute
  506. * @return boolean TRUE if it has been modified.
  507. */
  508. public function attribute_is_dirty($attribute)
  509. {
  510. return $this->__dirty && $this->__dirty[$attribute] && array_key_exists($attribute, $this->attributes);
  511. }
  512. /**
  513. * Returns a copy of the model's attributes hash.
  514. *
  515. * @return array A copy of the model's attribute data
  516. */
  517. public function attributes()
  518. {
  519. return $this->attributes;
  520. }
  521. /**
  522. * Retrieve the primary key name.
  523. *
  524. * @param boolean Set to true to return the first value in the pk array only
  525. * @return string The primary key for the model
  526. */
  527. public function get_primary_key($first=false)
  528. {
  529. $pk = static::table()->pk;
  530. return $first ? $pk[0] : $pk;
  531. }
  532. /**
  533. * Returns the actual attribute name if $name is aliased.
  534. *
  535. * @param string $name An attribute name
  536. * @return string
  537. */
  538. public function get_real_attribute_name($name)
  539. {
  540. if (array_key_exists($name,$this->attributes))
  541. return $name;
  542. if (array_key_exists($name,static::$alias_attribute))
  543. return static::$alias_attribute[$name];
  544. return null;
  545. }
  546. /**
  547. * Returns array of validator data for this Model.
  548. *
  549. * Will return an array looking like:
  550. *
  551. * <code>
  552. * array(
  553. * 'name' => array(
  554. * array('validator' => 'validates_presence_of'),
  555. * array('validator' => 'validates_inclusion_of', 'in' => array('Bob','Joe','John')),
  556. * 'password' => array(
  557. * array('validator' => 'validates_length_of', 'minimum' => 6))
  558. * )
  559. * );
  560. * </code>
  561. *
  562. * @return array An array containing validator data for this model.
  563. */
  564. public function get_validation_rules()
  565. {
  566. require_once 'Validations.php';
  567. $validator = new Validations($this);
  568. return $validator->rules();
  569. }
  570. /**
  571. * Returns an associative array containing values for all the attributes in $attributes
  572. *
  573. * @param array $attributes Array containing attribute names
  574. * @return array A hash containing $name => $value
  575. */
  576. public function get_values_for($attributes)
  577. {
  578. $ret = array();
  579. foreach ($attributes as $name)
  580. {
  581. if (array_key_exists($name,$this->attributes))
  582. $ret[$name] = $this->attributes[$name];
  583. }
  584. return $ret;
  585. }
  586. /**
  587. * Retrieves the name of the table for this Model.
  588. *
  589. * @return string
  590. */
  591. public static function table_name()
  592. {
  593. return static::table()->table;
  594. }
  595. /**
  596. * Returns the attribute name on the delegated relationship if $name is
  597. * delegated or null if not delegated.
  598. *
  599. * @param string $name Name of an attribute
  600. * @param array $delegate An array containing delegate data
  601. * @return delegated attribute name or null
  602. */
  603. private function is_delegated($name, &$delegate)
  604. {
  605. if ($delegate['prefix'] != '')
  606. $name = substr($name,strlen($delegate['prefix'])+1);
  607. if (is_array($delegate) && in_array($name,$delegate['delegate']))
  608. return $name;
  609. return null;
  610. }
  611. /**
  612. * Determine if the model is in read-only mode.
  613. *
  614. * @return boolean
  615. */
  616. public function is_readonly()
  617. {
  618. return $this->__readonly;
  619. }
  620. /**
  621. * Determine if the model is a new record.
  622. *
  623. * @return boolean
  624. */
  625. public function is_new_record()
  626. {
  627. return $this->__new_record;
  628. }
  629. /**
  630. * Throws an exception if this model is set to readonly.
  631. *
  632. * @throws ActiveRecord\ReadOnlyException
  633. * @param string $method_name Name of method that was invoked on model for exception message
  634. */
  635. private function verify_not_readonly($method_name)
  636. {
  637. if ($this->is_readonly())
  638. throw new ReadOnlyException(get_class($this), $method_name);
  639. }
  640. /**
  641. * Flag model as readonly.
  642. *
  643. * @param boolean $readonly Set to true to put the model into readonly mode
  644. */
  645. public function readonly($readonly=true)
  646. {
  647. $this->__readonly = $readonly;
  648. }
  649. /**
  650. * Retrieve the connection for this model.
  651. *
  652. * @return Connection
  653. */
  654. public static function connection()
  655. {
  656. return static::table()->conn;
  657. }
  658. /**
  659. * Re-establishes the database connection with a new connection.
  660. *
  661. * @return Connection
  662. */
  663. public static function reestablish_connection()
  664. {
  665. return static::table()->reestablish_connection();
  666. }
  667. /**
  668. * Returns the {@link Table} object for this model.
  669. *
  670. * Be sure to call in static scoping: static::table()
  671. *
  672. * @return Table
  673. */
  674. public static function table()
  675. {
  676. return Table::load(get_called_class());
  677. }
  678. /**
  679. * Creates a model and saves it to the database.
  680. *
  681. * @param array $attributes Array of the models attributes
  682. * @param boolean $validate True if the validators should be run
  683. * @return Model
  684. */
  685. public static function create($attributes, $validate=true)
  686. {
  687. $class_name = get_called_class();
  688. $model = new $class_name($attributes);
  689. $model->save($validate);
  690. return $model;
  691. }
  692. /**
  693. * Save the model to the database.
  694. *
  695. * This function will automatically determine if an INSERT or UPDATE needs to occur.
  696. * If a validation or a callback for this model returns false, then the model will
  697. * not be saved and this will return false.
  698. *
  699. * If saving an existing model only data that has changed will be saved.
  700. *
  701. * @param boolean $validate Set to true or false depending on if you want the validators to run or not
  702. * @return boolean True if the model was saved to the database otherwise false
  703. */
  704. public function save($validate=true)
  705. {
  706. $this->verify_not_readonly('save');
  707. return $this->is_new_record() ? $this->insert($validate) : $this->update($validate);
  708. }
  709. /**
  710. * Issue an INSERT sql statement for this model's attribute.
  711. *
  712. * @see save
  713. * @param boolean $validate Set to true or false depending on if you want the validators to run or not
  714. * @return boolean True if the model was saved to the database otherwise false
  715. */
  716. private function insert($validate=true)
  717. {
  718. $this->verify_not_readonly('insert');
  719. if (($validate && !$this->_validate() || !$this->invoke_callback('before_create',false)))
  720. return false;
  721. $table = static::table();
  722. if (!($attributes = $this->dirty_attributes()))
  723. $attributes = $this->attributes;
  724. $pk = $this->get_primary_key(true);
  725. $use_sequence = false;
  726. if ($table->sequence && !isset($attributes[$pk]))
  727. {
  728. if (($conn = static::connection()) instanceof OciAdapter)
  729. {
  730. // terrible oracle makes us select the nextval first
  731. $attributes[$pk] = $conn->get_next_sequence_value($table->sequence);
  732. $table->insert($attributes);
  733. $this->attributes[$pk] = $attributes[$pk];
  734. }
  735. else
  736. {
  737. // unset pk that was set to null
  738. if (array_key_exists($pk,$attributes))
  739. unset($attributes[$pk]);
  740. $table->insert($attributes,$pk,$table->sequence);
  741. $use_sequence = true;
  742. }
  743. }
  744. else
  745. $table->insert($attributes);
  746. // if we've got an autoincrementing/sequenced pk set it
  747. // don't need this check until the day comes that we decide to support composite pks
  748. // if (count($pk) == 1)
  749. {
  750. $column = $table->get_column_by_inflected_name($pk);
  751. if ($column->auto_increment || $use_sequence)
  752. $this->attributes[$pk] = static::connection()->insert_id($table->sequence);
  753. }
  754. $this->invoke_callback('after_create',false);
  755. $this->__new_record = false;
  756. return true;
  757. }
  758. /**
  759. * Issue an UPDATE sql statement for this model's dirty attributes.
  760. *
  761. * @see save
  762. * @param boolean $validate Set to true or false depending on if you want the validators to run or not
  763. * @return boolean True if the model was saved to the database otherwise false
  764. */
  765. private function update($validate=true)
  766. {
  767. $this->verify_not_readonly('update');
  768. if ($validate && !$this->_validate())
  769. return false;
  770. if ($this->is_dirty())
  771. {
  772. $pk = $this->values_for_pk();
  773. if (empty($pk))
  774. throw new ActiveRecordException("Cannot update, no primary key defined for: " . get_called_class());
  775. if (!$this->invoke_callback('before_update',false))
  776. return false;
  777. $dirty = $this->dirty_attributes();
  778. static::table()->update($dirty,$pk);
  779. $this->invoke_callback('after_update',false);
  780. }
  781. return true;
  782. }
  783. /**
  784. * Deletes records matching conditions in $options
  785. *
  786. * Does not instantiate models and therefore does not invoke callbacks
  787. *
  788. * Delete all using a hash:
  789. *
  790. * <code>
  791. * YourModel::delete_all(array('conditions' => array('name' => 'Tito')));
  792. * </code>
  793. *
  794. * Delete all using an array:
  795. *
  796. * <code>
  797. * YourModel::delete_all(array('conditions' => array('name = ?', 'Tito')));
  798. * </code>
  799. *
  800. * Delete all using a string:
  801. *
  802. * <code>
  803. * YourModel::delete_all(array('conditions' => 'name = "Tito"));
  804. * </code>
  805. *
  806. * An options array takes the following parameters:
  807. *
  808. * <ul>
  809. * <li><b>conditions:</b> Conditions using a string/hash/array</li>
  810. * <li><b>limit:</b> Limit number of records to delete (MySQL & Sqlite only)</li>
  811. * <li><b>order:</b> A SQL fragment for ordering such as: 'name asc', 'id desc, name asc' (MySQL & Sqlite only)</li>
  812. * </ul>
  813. *
  814. * @params array $options
  815. * return integer Number of rows affected
  816. */
  817. public static function delete_all($options=array())
  818. {
  819. $table = static::table();
  820. $conn = static::connection();
  821. $sql = new SQLBuilder($conn, $table->get_fully_qualified_table_name());
  822. $conditions = is_array($options) ? $options['conditions'] : $options;
  823. if (is_array($conditions) && !is_hash($conditions))
  824. call_user_func_array(array($sql, 'delete'), $conditions);
  825. else
  826. $sql->delete($conditions);
  827. if (isset($options['limit']))
  828. $sql->limit($options['limit']);
  829. if (isset($options['order']))
  830. $sql->order($options['order']);
  831. $values = $sql->bind_values();
  832. $ret = $conn->query(($table->last_sql = $sql->to_s()), $values);
  833. return $ret->rowCount();
  834. }
  835. /**
  836. * Updates records using set in $options
  837. *
  838. * Does not instantiate models and therefore does not invoke callbacks
  839. *
  840. * Update all using a hash:
  841. *
  842. * <code>
  843. * YourModel::update_all(array('set' => array('name' => "Bob")));
  844. * </code>
  845. *
  846. * Update all using a string:
  847. *
  848. * <code>
  849. * YourModel::update_all(array('set' => 'name = "Bob"'));
  850. * </code>
  851. *
  852. * An options array takes the following parameters:
  853. *
  854. * <ul>
  855. * <li><b>set:</b> String/hash of field names and their values to be updated with
  856. * <li><b>conditions:</b> Conditions using a string/hash/array</li>
  857. * <li><b>limit:</b> Limit number of records to update (MySQL & Sqlite only)</li>
  858. * <li><b>order:</b> A SQL fragment for ordering such as: 'name asc', 'id desc, name asc' (MySQL & Sqlite only)</li>
  859. * </ul>
  860. *
  861. * @params array $options
  862. * return integer Number of rows affected
  863. */
  864. public static function update_all($options=array())
  865. {
  866. $table = static::table();
  867. $conn = static::connection();
  868. $sql = new SQLBuilder($conn, $table->get_fully_qualified_table_name());
  869. $sql->update($options['set']);
  870. if (isset($options['conditions']) && ($conditions = $options['conditions']))
  871. {
  872. if (is_array($conditions) && !is_hash($conditions))
  873. call_user_func_array(array($sql, 'where'), $conditions);
  874. else
  875. $sql->where($conditions);
  876. }
  877. if (isset($options['limit']))
  878. $sql->limit($options['limit']);
  879. if (isset($options['order']))
  880. $sql->order($options['order']);
  881. $values = $sql->bind_values();
  882. $ret = $conn->query(($table->last_sql = $sql->to_s()), $values);
  883. return $ret->rowCount();
  884. }
  885. /**
  886. * Deletes this model from the database and returns true if successful.
  887. *
  888. * @return boolean
  889. */
  890. public function delete()
  891. {
  892. $this->verify_not_readonly('delete');
  893. $pk = $this->values_for_pk();
  894. if (empty($pk))
  895. throw new ActiveRecordException("Cannot delete, no primary key defined for: " . get_called_class());
  896. if (!$this->invoke_callback('before_destroy',false))
  897. return false;
  898. static::table()->delete($pk);
  899. $this->invoke_callback('after_destroy',false);
  900. return true;
  901. }
  902. /**
  903. * Helper that creates an array of values for the primary key(s).
  904. *
  905. * @return array An array in the form array(key_name => value, ...)
  906. */
  907. public function values_for_pk()
  908. {
  909. return $this->values_for(static::table()->pk);
  910. }
  911. /**
  912. * Helper to return a hash of values for the specified attributes.
  913. *
  914. * @param array $attribute_names Array of attribute names
  915. * @return array An array in the form array(name => value, ...)
  916. */
  917. public function values_for($attribute_names)
  918. {
  919. $filter = array();
  920. foreach ($attribute_names as $name)
  921. $filter[$name] = $this->$name;
  922. return $filter;
  923. }
  924. /**
  925. * Validates the model.
  926. *
  927. * @return boolean True if passed validators otherwise false
  928. */
  929. private function _validate()
  930. {
  931. require_once 'Validations.php';
  932. $validator = new Validations($this);
  933. $validation_on = 'validation_on_' . ($this->is_new_record() ? 'create' : 'update');
  934. foreach (array('before_validation', "before_$validation_on") as $callback)
  935. {
  936. if (!$this->invoke_callback($callback,false))
  937. return false;
  938. }
  939. // need to store reference b4 validating so that custom validators have access to add errors
  940. $this->errors = $validator->get_record();
  941. $validator->validate();
  942. foreach (array('after_validation', "after_$validation_on") as $callback)
  943. $this->invoke_callback($callback,false);
  944. if (!$this->errors->is_empty())
  945. return false;
  946. return true;
  947. }
  948. /**
  949. * Returns true if the model has been modified.
  950. *
  951. * @return boolean true if modified
  952. */
  953. public function is_dirty()
  954. {
  955. return empty($this->__dirty) ? false : true;
  956. }
  957. /**
  958. * Run validations on model and returns whether or not model passed validation.
  959. *
  960. * @see is_invalid
  961. * @return boolean
  962. */
  963. public function is_valid()
  964. {
  965. return $this->_validate();
  966. }
  967. /**
  968. * Runs validations and returns true if invalid.
  969. *
  970. * @see is_valid
  971. * @return boolean
  972. */
  973. public function is_invalid()
  974. {
  975. return !$this->_validate();
  976. }
  977. /**
  978. * Updates a model's timestamps.
  979. */
  980. public function set_timestamps()
  981. {
  982. $now = date('Y-m-d H:i:s');
  983. if (isset($this->updated_at))
  984. $this->updated_at = $now;
  985. if (isset($this->created_at) && $this->is_new_record())
  986. $this->created_at = $now;
  987. }
  988. /**
  989. * Mass update the model with an array of attribute data and saves to the database.
  990. *
  991. * @param array $attributes An attribute data array in the form array(name => value, ...)
  992. * @return boolean True if successfully updated and saved otherwise false
  993. */
  994. public function update_attributes($attributes)
  995. {
  996. $this->set_attributes($attributes);
  997. return $this->save();
  998. }
  999. /**
  1000. * Updates a single attribute and saves the record without going through the normal validation procedure.
  1001. *
  1002. * @param string $name Name of attribute
  1003. * @param mixed $value Value of the attribute
  1004. * @return boolean True if successful otherwise false
  1005. */
  1006. public function update_attribute($name, $value)
  1007. {
  1008. $this->__set($name, $value);
  1009. return $this->update(false);
  1010. }
  1011. /**
  1012. * Mass update the model with data from an attributes hash.
  1013. *
  1014. * Unlike update_attributes() this method only updates the model's data
  1015. * but DOES NOT save it to the database.
  1016. *
  1017. * @see update_attributes
  1018. * @param array $attributes An array containing data to update in the form array(name => value, ...)
  1019. */
  1020. public function set_attributes(array $attributes)
  1021. {
  1022. $this->set_attributes_via_mass_assignment($attributes, true);
  1023. }
  1024. /**
  1025. * Passing $guard_attributes as true will throw an exception if an attribute does not exist.
  1026. *
  1027. * @throws ActiveRecord\UndefinedPropertyException
  1028. * @param array $attributes An array in the form array(name => value, ...)
  1029. * @param boolean $guard_attributes Flag of whether or not protected/non-accessible attributes should be guarded
  1030. */
  1031. private function set_attributes_via_mass_assignment(array &$attributes, $guard_attributes)
  1032. {
  1033. //access uninflected columns since that is what we would have in result set
  1034. $table = static::table();
  1035. $exceptions = array();
  1036. $use_attr_accessible = !empty(static::$attr_accessible);
  1037. $use_attr_protected = !empty(static::$attr_protected);
  1038. $connection = static::connection();
  1039. foreach ($attributes as $name => $value)
  1040. {
  1041. // is a normal field on the table
  1042. if (array_key_exists($name,$table->columns))
  1043. {
  1044. $value = $table->columns[$name]->cast($value,$connection);
  1045. $name = $table->columns[$name]->inflected_name;
  1046. }
  1047. if ($guard_attributes)
  1048. {
  1049. if ($use_attr_accessible && !in_array($name,static::$attr_accessible))
  1050. continue;
  1051. if ($use_attr_protected && in_array($name,static::$attr_protected))
  1052. continue;
  1053. // set valid table data
  1054. try {
  1055. $this->$name = $value;
  1056. } catch (UndefinedPropertyException $e) {
  1057. $exceptions[] = $e->getMessage();
  1058. }
  1059. }
  1060. else
  1061. {
  1062. // ignore OciAdapter's limit() stuff
  1063. if ($name == 'ar_rnum__')
  1064. continue;
  1065. // set arbitrary data
  1066. $this->assign_attribute($name,$value);
  1067. }
  1068. }
  1069. if (!empty($exceptions))
  1070. throw new UndefinedPropertyException(get_called_class(),$exceptions);
  1071. }
  1072. /**
  1073. * Add a model to the given named ($name) relationship.
  1074. *
  1075. * @internal This should <strong>only</strong> be used by eager load
  1076. * @param Model $model
  1077. * @param $name of relationship for this table
  1078. * @return void
  1079. */
  1080. public function set_relationship_from_eager_load(Model $model=null, $name)
  1081. {
  1082. $table = static::table();
  1083. if (($rel = $table->get_relationship($name)))
  1084. {
  1085. if ($rel->is_poly())
  1086. {
  1087. // if the related model is null and it is a poly then we should have an empty array
  1088. if (is_null($model))
  1089. return $this->__relationships[$name] = array();
  1090. else
  1091. return $this->__relationships[$name][] = $model;
  1092. }
  1093. else
  1094. return $this->__relationships[$name] = $model;
  1095. }
  1096. throw new RelationshipException("Relationship named $name has not been declared for class: {$table->class->getName()}");
  1097. }
  1098. /**
  1099. * Reloads the attributes and relationships of this object from the database.
  1100. *
  1101. * @return Model
  1102. */
  1103. public function reload()
  1104. {
  1105. $this->__relationships = array();
  1106. $pk = array_values($this->get_values_for($this->get_primary_key()));
  1107. $this->set_attributes_via_mass_assignment($this->find($pk)->attributes, false);
  1108. $this->reset_dirty();
  1109. return $this;
  1110. }
  1111. public function __clone()
  1112. {
  1113. $this->__relationships = array();
  1114. $this->reset_dirty();
  1115. return $this;
  1116. }
  1117. /**
  1118. * Resets the dirty array.
  1119. *
  1120. * @see dirty_attributes
  1121. */
  1122. public function reset_dirty()
  1123. {
  1124. $this->__dirty = null;
  1125. }
  1126. /**
  1127. * A list of valid finder options.
  1128. *
  1129. * @var array
  1130. */
  1131. static $VALID_OPTIONS = array('conditions', 'limit', 'offset', 'order', 'select', 'joins', 'include', 'readonly', 'group', 'from', 'having');
  1132. /**
  1133. * Enables the use of dynamic finders.
  1134. *
  1135. * Dynamic finders are just an easy way to do queries quickly without having to
  1136. * specify an options array with conditions in it.
  1137. *
  1138. * <code>
  1139. * SomeModel::find_by_first_name('Tito');
  1140. * SomeModel::find_by_first_name_and_last_name('Tito','the Grief');
  1141. * SomeModel::find_by_first_name_or_last_name('Tito','the Grief');
  1142. * SomeModel::find_all_by_last_name('Smith');
  1143. * SomeModel::count_by_name('Bob')
  1144. * SomeModel::count_by_name_or_state('Bob','VA')
  1145. * SomeModel::count_by_name_and_state('Bob','VA')
  1146. * </code>
  1147. *
  1148. * You can also create the model if the find call returned no results:
  1149. *
  1150. * <code>
  1151. * Person::find_or_create_by_name('Tito');
  1152. *
  1153. * # would be the equivalent of
  1154. * if (!Person::find_by_name('Tito'))
  1155. * Person::create(array('Tito'));
  1156. * </code>
  1157. *
  1158. * Some other examples of find_or_create_by:
  1159. *
  1160. * <code>
  1161. * Person::find_or_create_by_name_and_id('Tito',1);
  1162. * Person::find_or_create_by_name_and_id(array('name' => 'Tito', 'id' => 1));
  1163. * </code>
  1164. *
  1165. * @param string $method Name of method
  1166. * @param mixed $args Method args
  1167. * @return Model
  1168. * @throws {@link ActiveRecordException} if invalid query
  1169. * @see find
  1170. */
  1171. public static function __callStatic($method, $args)
  1172. {
  1173. $options = static::extract_and_validate_options($args);
  1174. $create = false;
  1175. if (substr($method,0,17) == 'find_or_create_by')
  1176. {
  1177. $attributes = substr($method,17);
  1178. // can't take any finders with OR in it when doing a find_or_create_by
  1179. if (strpos($attributes,'_or_') !== false)
  1180. throw new ActiveRecordException("Cannot use OR'd attributes in find_or_create_by");
  1181. $create = true;
  1182. $method = 'find_by' . substr($method,17);
  1183. }
  1184. if (substr($method,0,7) === 'find_by')
  1185. {
  1186. $attributes = substr($method,8);
  1187. $options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),$attributes,$args,static::$alias_attribute);
  1188. if (!($ret = static::find('first',$options)) && $create)
  1189. return static::create(SQLBuilder::create_hash_from_underscored_string($attributes,$args,static::$alias_attribute));
  1190. return $ret;
  1191. }
  1192. elseif (substr($method,0,11) === 'find_all_by')
  1193. {
  1194. $options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),substr($method,12),$args,static::$alias_attribute);
  1195. return static::find('all',$options);
  1196. }
  1197. elseif (substr($method,0,8) === 'count_by')
  1198. {
  1199. $options['conditions'] = SQLBuilder::create_conditions_from_underscored_string(static::connection(),substr($method,9),$args,static::$alias_attribute);
  1200. return static::count($options);
  1201. }
  1202. throw new ActiveRecordException("Call to undefined method: $method");
  1203. }
  1204. /**
  1205. * Enables the use of build|create for associations.
  1206. *
  1207. * @param string $method Name of method
  1208. * @param mixed $args Method args
  1209. * @return mixed An instance of a given {@link AbstractRelationship}
  1210. */
  1211. public function __call($method, $args)
  1212. {
  1213. //check for build|create_association methods
  1214. if (preg_match('/(build|create)_/', $method))
  1215. {
  1216. if (!empty($args))
  1217. $args = $args[0];
  1218. $association_name = str_replace(array('build_', 'create_'), '', $method);
  1219. $method = str_replace($association_name, 'association', $method);
  1220. $table = static::table();
  1221. if (($association = $table->get_relationship($association_name)) ||
  1222. ($association = $table->get_relationship(($association_name = Utils::pluralize($association_name)))))
  1223. {
  1224. // access association to ensure that the relationship has been loaded
  1225. // so that we do not double-up on records if we append a newly created
  1226. $this->$association_name;
  1227. return $association->$method($this, $args);
  1228. }
  1229. }
  1230. throw new ActiveRecordException("Call to undefined method: $method");
  1231. }
  1232. /**
  1233. * Alias for self::find('all').
  1234. *
  1235. * @see find
  1236. * @return array array of records found
  1237. */
  1238. public static function all(/* ... */)
  1239. {
  1240. return call_user_func_array('static::find',array_merge(array('all'),func_get_args()));
  1241. }
  1242. /**
  1243. * Get a count of qualifying records.
  1244. *
  1245. * <code>
  1246. * YourModel::count(array('conditions' => 'amount > 3.14159265'));
  1247. * </code>
  1248. *
  1249. * @see find
  1250. * @return int Number of records that matched the query
  1251. */
  1252. public static function count(/* ... */)
  1253. {
  1254. $args = func_get_args();
  1255. $options = static::extract_and_validate_options($args);
  1256. $options['select'] = 'COUNT(*)';
  1257. if (!empty($args) && !is_null($args[0]) && !empty($args[0]))
  1258. {
  1259. if (is_hash($args[0]))
  1260. $options['conditions'] = $args[0];
  1261. else
  1262. $options['conditions'] = call_user_func_array('static::pk_conditions',$args);
  1263. }
  1264. $table = static::table();
  1265. $sql = $table->options_to_sql($options);
  1266. $values = $sql->get_where_values();
  1267. return static::connection()->query_and_fetch_one($sql->to_s(),$values);
  1268. }
  1269. /**
  1270. * Determine if a record exists.
  1271. *
  1272. * <code>
  1273. * SomeModel::exists(123);
  1274. * SomeModel::exists(array('conditions' => array('id=? and name=?', 123, 'Tito')));
  1275. * SomeModel::exists(array('id' => 123, 'name' => 'Tito'));
  1276. * </code>
  1277. *
  1278. * @see find
  1279. * @return boolean
  1280. */
  1281. public static function exists(/* ... */)
  1282. {
  1283. return call_user_func_array('static::count',func_get_args()) > 0 ? true : false;
  1284. }
  1285. /**
  1286. * Alias for self::find('first').
  1287. *
  1288. * @see find
  1289. * @return Model The first matched record or null if not found
  1290. */
  1291. public static function first(/* ... */)
  1292. {
  1293. return call_user_func_array('static::find',array_merge(array('first'),func_get_args()));
  1294. }
  1295. /**
  1296. * Alias for self::find('last')
  1297. *
  1298. * @see find
  1299. * @return Model The last matched record or null if not found
  1300. */
  1301. public static function last(/* ... */)
  1302. {
  1303. return call_user_func_array('static::find',array_merge(array('last'),func_get_args()));
  1304. }
  1305. /**
  1306. * Find records in the database.
  1307. *
  1308. * Finding by the primary key:
  1309. *
  1310. * <code>
  1311. * # queries for the model with id=123
  1312. * YourModel::find(123);
  1313. *
  1314. * # queries for model with id in(1,2,3)
  1315. * YourModel::find(1,2,3);
  1316. *
  1317. * # finding by pk accepts an options array
  1318. * YourModel::find(123,array('order' => 'name desc'));
  1319. * </code>
  1320. *
  1321. * Finding by using a conditions array:
  1322. *
  1323. * <code>
  1324. * YourModel::find('first', array('conditions' => array('name=?','Tito'),
  1325. * 'order' => 'name asc'))
  1326. * YourModel::find('all', array('conditions' => 'amount > 3.14159265'));
  1327. * YourModel::find('all', array('conditions' => array('id in(?)', array(1,2,3))));
  1328. * </code>
  1329. *
  1330. * Finding by using a hash:
  1331. *
  1332. * <code>
  1333. * YourModel::find(array('name' => 'Tito', 'id' => 1));
  1334. * YourModel::find('first',array('name' => 'Tito', 'id' => 1));
  1335. * YourModel::find('all',array('name' => 'Tito', 'id' => 1));
  1336. * </code>
  1337. *
  1338. * An options array can take the following parameters:
  1339. *
  1340. * <ul>
  1341. * <li><b>select:</b> A SQL fragment for what fields to return such as: '*', 'people.*', 'first_name, last_name, id'</li>
  1342. * <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>
  1343. * <li><b>include:</b> TODO not implemented yet</li>
  1344. * <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')),
  1345. * array('name' => 'Tito', 'id' => 1)</li>
  1346. * <li><b>limit:</b> Number of records to limit the query to</li>
  1347. * <li><b>offset:</b> The row offset to return results from for the query</li>
  1348. * <li><b>order:</b> A SQL fragment for order such as: 'name asc', 'name asc, id desc'</li>
  1349. * <li><b>readonly:</b> Return all the models in readonly mode</li>
  1350. * <li><b>group:</b> A SQL group by fragment</li>
  1351. * </ul>
  1352. *
  1353. * @throws {@link RecordNotFound} if no options are passed or finding by pk and no records matched
  1354. * @return mixed An array of records found if doing a find_all otherwise a
  1355. * single Model object or null if it wasn't found. NULL is only return when
  1356. * doing a first/last find. If doing an all find and no records matched this
  1357. * will return an empty array.
  1358. */
  1359. public static function find(/* $type, $options */)
  1360. {
  1361. $class = get_called_class();
  1362. if (func_num_args() <= 0)
  1363. throw new RecordNotFound("Couldn't find $class without an ID");
  1364. $args = func_get_args();
  1365. $options = static::extract_and_validate_options($args);
  1366. $num_args = count($args);
  1367. $single = true;
  1368. if ($num_args > 0 && ($args[0] === 'all' || $args[0] === 'first' || $args[0] === 'last'))
  1369. {
  1370. switch ($args[0])
  1371. {
  1372. case 'all':
  1373. $single = false;
  1374. break;
  1375. case 'last':
  1376. if (!array_key_exists('order',$options))
  1377. $options['order'] = join(' DESC, ',static::table()->pk) . ' DESC';
  1378. else
  1379. $options['order'] = SQLBuilder::reverse_order($options['order']);
  1380. // fall thru
  1381. case 'first':
  1382. $options['limit'] = 1;
  1383. $options['offset'] = 0;
  1384. break;
  1385. }
  1386. $args = array_slice($args,1);
  1387. $num_args--;
  1388. }
  1389. //find by pk
  1390. elseif (1 === count($args) && 1 == $num_args)
  1391. $args = $args[0];
  1392. // anything left in $args is a find by pk
  1393. if ($num_args > 0 && !isset($options['conditions']))
  1394. return static::find_by_pk($args, $options);
  1395. $options['mapped_names'] = static::$alias_attribute;
  1396. $list = static::table()->find($options);
  1397. return $single ? (!empty($list) ? $list[0] : null) : $list;
  1398. }
  1399. /**
  1400. * Finder method which will find by a single or array of primary keys for this model.
  1401. *
  1402. * @see find
  1403. * @param array $values An array containing values for the pk
  1404. * @param array $options An options array
  1405. * @return Model
  1406. * @throws {@link RecordNotFound} if a record could not be found
  1407. */
  1408. public static function find_by_pk($values, $options)
  1409. {
  1410. $options['conditions'] = static::pk_conditions($values);
  1411. $list = static::table()->find($options);
  1412. $results = count($list);
  1413. if ($results != ($expected = count($values)))
  1414. {
  1415. $class = get_called_class();
  1416. if ($expected == 1)
  1417. {
  1418. if (!is_array($values))
  1419. $values = array($values);
  1420. throw new RecordNotFound("Couldn't find $class with ID=" . join(',',$values));
  1421. }
  1422. $values = join(',',$values);
  1423. throw new RecordNotFound("Couldn't find all $class with IDs ($values) (found $results, but was looking for $expected)");
  1424. }
  1425. return $expected == 1 ? $list[0] : $list;
  1426. }
  1427. /**
  1428. * Find using a raw SELECT query.
  1429. *
  1430. * <code>
  1431. * YourModel::find_by_sql("SELECT * FROM people WHERE name=?",array('Tito'));
  1432. * YourModel::find_by_sql("SELECT * FROM people WHERE name='Tito'");
  1433. * </code>
  1434. *
  1435. * @param string $sql The raw SELECT query
  1436. * @param array $values An array of values for any parameters that needs to be bound
  1437. * @return array An array of models
  1438. */
  1439. public static function find_by_sql($sql, $values=null)
  1440. {
  1441. return static::table()->find_by_sql($sql, $values, true);
  1442. }
  1443. /**
  1444. * Helper method to run arbitrary queries against the model's database connection.
  1445. *
  1446. * @param string $sql SQL to execute
  1447. * @param array $values Bind values, if any, for the query
  1448. * @return object A PDOStatement object
  1449. */
  1450. public static function query($sql, $values=null)
  1451. {
  1452. return static::connection()->query($sql, $values);
  1453. }
  1454. /**
  1455. * Determines if the specified array is a valid ActiveRecord options array.
  1456. *
  1457. * @param array $array An options array
  1458. * @param bool $throw True to throw an exception if not valid
  1459. * @return boolean True if valid otherwise valse
  1460. * @throws {@link ActiveRecordException} if the array contained any invalid options
  1461. */
  1462. public static function is_options_hash($array, $throw=true)
  1463. {
  1464. if (is_hash($array))
  1465. {
  1466. $keys = array_keys($array);
  1467. $diff = array_diff($keys,self::$VALID_OPTIONS);
  1468. if (!empty($diff) && $throw)
  1469. throw new ActiveRecordException("Unknown key(s): " . join(', ',$diff));
  1470. $intersect = array_intersect($keys,self::$VALID_OPTIONS);
  1471. if (!empty($intersect))
  1472. return true;
  1473. }
  1474. return false;
  1475. }
  1476. /**
  1477. * Returns a hash containing the names => values of the primary key.
  1478. *
  1479. * @internal This needs to eventually support composite keys.
  1480. * @param mixed $args Primary key value(s)
  1481. * @return array An array in the form array(name => value, ...)
  1482. */
  1483. public static function pk_conditions($args)
  1484. {
  1485. $table = static::table();
  1486. $ret = array($table->pk[0] => $args);
  1487. return $ret;
  1488. }
  1489. /**
  1490. * Pulls out the options hash from $array if any.
  1491. *
  1492. * @internal DO NOT remove the reference on $array.
  1493. * @param array &$array An array
  1494. * @return array A valid options array
  1495. */
  1496. public static function extract_and_validate_options(array &$array)
  1497. {
  1498. $options = array();
  1499. if ($array)
  1500. {
  1501. $last = &$array[count($array)-1];
  1502. try
  1503. {
  1504. if (self::is_options_hash($last))
  1505. {
  1506. array_pop($array);
  1507. $options = $last;
  1508. }
  1509. }
  1510. catch (ActiveRecordException $e)
  1511. {
  1512. if (!is_hash($last))
  1513. throw $e;
  1514. $options = array('conditions' => $last);
  1515. }
  1516. }
  1517. return $options;
  1518. }
  1519. /**
  1520. * Returns a JSON representation of this model.
  1521. *
  1522. * @see Serialization
  1523. * @param array $options An array containing options for json serialization (see {@link Serialization} for valid options)
  1524. * @return string JSON representation of the model
  1525. */
  1526. public function to_json(array $options=array())
  1527. {
  1528. return $this->serialize('Json', $options);
  1529. }
  1530. /**
  1531. * Returns an XML representation of this model.
  1532. *
  1533. * @see Serialization
  1534. * @param array $options An array containing options for xml serialization (see {@link Serialization} for valid options)
  1535. * @return string XML representation of the model
  1536. */
  1537. public function to_xml(array $options=array())
  1538. {
  1539. return $this->serialize('Xml', $options);
  1540. }
  1541. /**
  1542. * Returns an CSV representation of this model.
  1543. * Can take optional delimiter and enclosure
  1544. * (defaults are , and double quotes)
  1545. *
  1546. * Ex:
  1547. * <code>
  1548. * ActiveRecord\CsvSerializer::$delimiter=';';
  1549. * ActiveRecord\CsvSerializer::$enclosure='';
  1550. * YourModel::find('first')->to_csv(array('only'=>array('name','level')));
  1551. * returns: Joe,2
  1552. *
  1553. * YourModel::find('first')->to_csv(array('only_header'=>true,'only'=>array('name','level')));
  1554. * returns: name,level
  1555. * </code>
  1556. *
  1557. * @see Serialization
  1558. * @param array $options An array containing options for csv serialization (see {@link Serialization} for valid options)
  1559. * @return string CSV representation of the model
  1560. */
  1561. public function to_csv(array $options=array())
  1562. {
  1563. return $this->serialize('Csv', $options);
  1564. }
  1565. /**
  1566. * Returns an Array representation of this model.
  1567. *
  1568. * @see Serialization
  1569. * @param array $options An array containing options for json serialization (see {@link Serialization} for valid options)
  1570. * @return array Array representation of the model
  1571. */
  1572. public function to_array(array $options=array())
  1573. {
  1574. return $this->serialize('Array', $options);
  1575. }
  1576. /**
  1577. * Creates a serializer based on pre-defined to_serializer()
  1578. *
  1579. * An options array can take the following parameters:
  1580. *
  1581. * <ul>
  1582. * <li><b>only:</b> a string or array of attributes to be included.</li>
  1583. * <li><b>excluded:</b> a string or array of attributes to be excluded.</li>
  1584. * <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
  1585. * along with the method's returned value</li>
  1586. * <li><b>include:</b> a string or array of associated models to include in the final serialized product.</li>
  1587. * </ul>
  1588. *
  1589. * @param string $type Either Xml, Json, Csv or Array
  1590. * @param array $options Options array for the serializer
  1591. * @return string Serialized representation of the model
  1592. */
  1593. private function serialize($type, $options)
  1594. {
  1595. require_once 'Serialization.php';
  1596. $class = "ActiveRecord\\{$type}Serializer";
  1597. $serializer = new $class($this, $options);
  1598. return $serializer->to_s();
  1599. }
  1600. /**
  1601. * Invokes the specified callback on this model.
  1602. *
  1603. * @param string $method_name Name of the call back to run.
  1604. * @param boolean $must_exist Set to true to raise an exception if the callback does not exist.
  1605. * @return boolean True if invoked or null if not
  1606. */
  1607. private function invoke_callback($method_name, $must_exist=true)
  1608. {
  1609. return static::table()->callback->invoke($this,$method_name,$must_exist);
  1610. }
  1611. /**
  1612. * Executes a block of code inside a database transaction.
  1613. *
  1614. * <code>
  1615. * YourModel::transaction(function()
  1616. * {
  1617. * YourModel::create(array("name" => "blah"));
  1618. * });
  1619. * </code>
  1620. *
  1621. * If an exception is thrown inside the closure the transaction will
  1622. * automatically be rolled back. You can also return false from your
  1623. * closure to cause a rollback:
  1624. *
  1625. * <code>
  1626. * YourModel::transaction(function()
  1627. * {
  1628. * YourModel::create(array("name" => "blah"));
  1629. * throw new Exception("rollback!");
  1630. * });
  1631. *
  1632. * YourModel::transaction(function()
  1633. * {
  1634. * YourModel::create(array("name" => "blah"));
  1635. * return false; # rollback!
  1636. * });
  1637. * </code>
  1638. *
  1639. * @param Closure $closure The closure to execute. To cause a rollback have your closure return false or throw an exception.
  1640. * @return boolean True if the transaction was committed, False if rolled back.
  1641. */
  1642. public static function transaction($closure)
  1643. {
  1644. $connection = static::connection();
  1645. try
  1646. {
  1647. $connection->transaction();
  1648. if ($closure() === false)
  1649. {
  1650. $connection->rollback();
  1651. return false;
  1652. }
  1653. else
  1654. $connection->commit();
  1655. }
  1656. catch (\Exception $e)
  1657. {
  1658. $connection->rollback();
  1659. throw $e;
  1660. }
  1661. return true;
  1662. }
  1663. };
  1664. ?>