PageRenderTime 53ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/core/src/main/php/rdbms/DataSet.class.php

https://github.com/treuter/xp-framework
PHP | 389 lines | 137 code | 27 blank | 225 comment | 8 complexity | 552e44a3928189456668849092e6eb10 MD5 | raw file
  1. <?php
  2. /* This class is part of the XP framework
  3. *
  4. * $Id$
  5. */
  6. uses(
  7. 'rdbms.ConnectionManager',
  8. 'rdbms.Peer',
  9. 'rdbms.Criteria',
  10. 'rdbms.FieldType',
  11. 'rdbms.join.JoinExtractable'
  12. );
  13. /**
  14. * A dataset represents a row of data selected from a database. Dataset
  15. * classes usually provide getters and setters for every field in
  16. * addition to insert(), update() and delete() methods and one or more
  17. * static methods are provided to retrieve datasets from the database.
  18. *
  19. * Note: All of these methods will rely on an instance of
  20. * rdbms.ConnectionManager having been setup with a suitable connection.
  21. * This way, there is no need to pass a connection instance to every
  22. * single method.
  23. *
  24. * For example, a table containing news might provide a getByDate()
  25. * method which returns an array of news objects and a getByNewsId()
  26. * method returning one object.
  27. *
  28. * The basic ways to use the abovementioned example class would be:
  29. *
  30. * 1) Retrieve a news object
  31. * <code>
  32. * try {
  33. * $news= News::getByNewsId($id);
  34. * } catch (SQLException $e) {
  35. * $e->printStackTrace();
  36. * exit(-1);
  37. * }
  38. *
  39. * echo $news->toString();
  40. * </code>
  41. *
  42. * 2) Create a new entry
  43. * <code>
  44. * with ($n= new News()); {
  45. * $n->setCategoryId($cat);
  46. * $n->setTitle('Welcome');
  47. * $n->setBody(NULL);
  48. * $n->setAuthor('Timm');
  49. * $n->setCreatedAt(Date::now());
  50. *
  51. * try {
  52. * $n->insert();
  53. * } catch (SQLException $e) {
  54. * $e->printStackTrace();
  55. * exit(-1);
  56. * }
  57. *
  58. * echo $n->toString();
  59. * }
  60. * </code>
  61. *
  62. * 3) Modify a news object
  63. * <code>
  64. * try {
  65. * with ($news= News::getByNewsId($id)); {
  66. * $news->setCaption('Good news, everyone!');
  67. * $news->setAuthor('Hubert Farnsworth');
  68. * $news->update();
  69. * }
  70. * } catch (SQLException $e) {
  71. * $e->printStackTrace();
  72. * exit(-1);
  73. * }
  74. *
  75. * echo $news->toString();
  76. * </code>
  77. *
  78. * @test xp://net.xp_framework.unittest.rdbms.DataSetTest
  79. * @see xp://rdbms.Peer
  80. * @see xp://rdbms.ConnectionManager
  81. * @purpose Base class
  82. */
  83. abstract class DataSet extends Object implements JoinExtractable {
  84. public
  85. $_new = TRUE,
  86. $_changed = array();
  87. protected
  88. $cache= array(),
  89. $cached= array();
  90. /**
  91. * Constructor. Supports the array syntax, where an associative
  92. * array is passed to the constructor, the keys being the member
  93. * variables and the values the member's values.
  94. *
  95. * @param array params default NULL
  96. */
  97. public function __construct($params= NULL) {
  98. if (is_array($params)) {
  99. foreach (array_keys($params) as $key) {
  100. $k= substr(strrchr('#'.$key, '#'), 1);
  101. $this->{$k}= $params[$key];
  102. }
  103. $this->_new= FALSE;
  104. }
  105. }
  106. /**
  107. * Retrieve associated peer
  108. *
  109. * @return rdbms.Peer
  110. */
  111. public static function getPeer() { }
  112. /**
  113. * implements JoinExtractable
  114. * add object to cache
  115. *
  116. * @param string role name of relation
  117. * @param string key unique ojbect key
  118. * @param lang.object obj
  119. */
  120. public function setCachedObj($role, $key, $obj) {
  121. $this->cache[$role][$key]= $obj;
  122. }
  123. /**
  124. * implements JoinExtractable
  125. * get cached object
  126. *
  127. * @param string role name of relation
  128. * @param string key unique ojbect key
  129. * @return lang.object obj
  130. */
  131. public function getCachedObj($role, $key) {
  132. return $this->cache[$role][$key];
  133. }
  134. /**
  135. * implements JoinExtractable
  136. * test if obect with key ist cached
  137. *
  138. * @param string role name of relation
  139. * @param string key unique ojbect key
  140. * @return bool
  141. */
  142. public function hasCachedObj($role, $key) {
  143. return isset($this->cache[$role][$key]);
  144. }
  145. /**
  146. * implements JoinExtractable
  147. * mark role as cached
  148. *
  149. * @param string role name of relation
  150. * @return bool
  151. */
  152. public function markAsCached($role) {
  153. $this->cached[$role]= TRUE;
  154. }
  155. /**
  156. * Changes a value by a specified key and returns the previous value.
  157. *
  158. * @param string key
  159. * @param var value
  160. * @return var previous value
  161. */
  162. protected function _change($key, $value) {
  163. $this->_changed[$key]= $value;
  164. $previous= $this->{$key};
  165. $this->{$key}= $value;
  166. return $previous;
  167. }
  168. /**
  169. * Sets a field's value by the field's name and returns the previous value.
  170. *
  171. * @param string field name
  172. * @param var value
  173. * @return var previous value
  174. * @throws lang.IllegalArgumentException in case the field does not exist
  175. */
  176. public function set($field, $value) {
  177. if (!isset(Peer::forInstance($this)->types[$field])) {
  178. throw new IllegalArgumentException('Field "'.$field.'" does not exist for DataSet '.$this->getClassName());
  179. }
  180. return $this->_change($field, $value);
  181. }
  182. /**
  183. * Gets a field's value by the field's name
  184. *
  185. * @param string field name
  186. * @throws lang.IllegalArgumentException in case the field does not exist
  187. */
  188. public function get($field) {
  189. if (!isset(Peer::forInstance($this)->types[$field])) {
  190. throw new IllegalArgumentException('Field "'.$field.'" does not exist for DataSet '.$this->getClassName());
  191. }
  192. return $this->{$field};
  193. }
  194. /**
  195. * Returns an array of fields that were changed suitable for passing
  196. * to Peer::doInsert() and Peer::doUpdate()
  197. *
  198. * @return array
  199. */
  200. public function changes() {
  201. return $this->_changed;
  202. }
  203. /**
  204. * Returns whether this record is new
  205. *
  206. * @return bool
  207. */
  208. public function isNew() {
  209. return $this->_new;
  210. }
  211. /**
  212. * Creates a string representation of this dataset. In this default
  213. * implementation, it will look like the following:
  214. *
  215. * <pre>
  216. * de.thekid.db.News(0.86699200 1086297326)@{
  217. * [newsId PK,I] 76288
  218. * [categoryId ] 12
  219. * [caption ] 'Hello'
  220. * [body ] NULL
  221. * [author ] 'Timm'
  222. * [createdAt ] Thu, 3 Jun 2004 22:26:15 +0200
  223. * }
  224. * </pre>
  225. *
  226. * Note: Keys with a leading "_" will be omitted from the list, they
  227. * indicate "protected" members.
  228. *
  229. * @return string
  230. */
  231. public function toString() {
  232. $peer= $this->getPeer();
  233. // Retrieve types from peer and figure out the maximum length
  234. // of a key which will be used for the key "column". The minimum
  235. // width of this column is 20 characters.
  236. $max= 0xF;
  237. foreach (array_keys($peer->types) as $key) {
  238. $max= max($max, strlen($key));
  239. }
  240. $fmt= ' [%-'.$max.'s %2s%2s] %s';
  241. // Build string representation.
  242. $s= $this->getClassName().'@('.$this->hashCode()."){\n";
  243. foreach (array_keys($peer->types) as $key) {
  244. $s.= sprintf(
  245. $fmt,
  246. $key,
  247. (in_array($key, $peer->primary) ? 'PK' : ''),
  248. ($key == $peer->identity ? ',I' : ''),
  249. xp::stringOf($this->$key)
  250. )."\n";
  251. }
  252. return $s.'}';
  253. }
  254. /**
  255. * Update this object in the database by specified criteria
  256. *
  257. * @return var identity value if applicable, else NULL
  258. * @throws rdbms.SQLException in case an error occurs
  259. */
  260. public function doInsert() {
  261. $peer= $this->getPeer();
  262. if ($id= $peer->doInsert($this->_changed)) {
  263. // Set identity value if requested. We do not use the _change()
  264. // method here since the primary key is not supposed to appear
  265. // in the list of changed attributes
  266. $this->{$peer->identity}= $id;
  267. }
  268. $this->_changed= array();
  269. return $id;
  270. }
  271. /**
  272. * Update this object in the database by specified criteria
  273. *
  274. * @param rdbms.SQLExpression criteria
  275. * @return int number of affected rows
  276. * @throws rdbms.SQLException in case an error occurs
  277. */
  278. public function doUpdate(SQLExpression $criteria) {
  279. $affected= $this->getPeer()->doUpdate($this->_changed, $criteria);
  280. $this->_changed= array();
  281. return $affected;
  282. }
  283. /**
  284. * Delete this object from the database by specified criteria
  285. *
  286. * @param rdbms.SQLExpression criteria
  287. * @return int number of affected rows
  288. * @throws rdbms.SQLException in case an error occurs
  289. */
  290. public function doDelete(SQLExpression $criteria) {
  291. $affected= $this->getPeer()->doDelete($criteria);
  292. $this->_changed= array();
  293. return $affected;
  294. }
  295. /**
  296. * Insert this dataset (create a new row in the table).
  297. *
  298. * @return var identity value if applicable, else NULL
  299. * @throws rdbms.SQLException
  300. */
  301. public function insert() {
  302. $identity= $this->doInsert();
  303. $this->_new= FALSE;
  304. return $identity;
  305. }
  306. /**
  307. * Update this dataset (change an existing row in the table).
  308. * Updates the record by using the primary key(s) as criteria.
  309. *
  310. * @return int affected rows
  311. * @throws rdbms.SQLException
  312. */
  313. public function update() {
  314. if (empty($this->_changed)) return 0;
  315. $peer= $this->getPeer();
  316. if (empty($peer->primary)) {
  317. throw new SQLStateException('No primary key for table '.$peer->getTable());
  318. }
  319. $criteria= new Criteria();
  320. foreach ($peer->primary as $key) {
  321. $criteria->add($key, $this->{$key}, EQUAL);
  322. }
  323. $affected= $peer->doUpdate($this->_changed, $criteria);
  324. $this->_changed= array();
  325. return $affected;
  326. }
  327. /**
  328. * Save this dataset. Inserts if this dataset is new (that means: Has been
  329. * created by new DataSetName()) and updates if it has been retrieved by
  330. * the database (by means of doSelect(), getBy...() or iterators).
  331. *
  332. * @return var identity value if applicable, else NULL
  333. * @throws rdbms.SQLException
  334. */
  335. public function save() {
  336. $peer= $this->getPeer();
  337. $this->_new ? $this->insert() : $this->update();
  338. return $peer->identity ? $this->{$peer->identity} : NULL;
  339. }
  340. /**
  341. * Delete this dataset (remove the corresponding row from the table).
  342. * Does nothing in this default implementation and may be overridden
  343. * in subclasses where it makes sense.
  344. *
  345. * @return int affected rows
  346. * @throws rdbms.SQLException
  347. */
  348. public function delete() {
  349. $peer= $this->getPeer();
  350. if (empty($peer->primary)) {
  351. throw new SQLStateException('No primary key for table '.$peer->getTable());
  352. }
  353. $criteria= new Criteria();
  354. foreach ($peer->primary as $key) {
  355. $criteria->add($key, $this->{$key}, EQUAL);
  356. }
  357. $affected= $peer->doDelete($criteria);
  358. $this->_changed= array();
  359. return $affected;
  360. }
  361. }
  362. ?>