PageRenderTime 48ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/Db/Table/Adapter.php

https://github.com/deviousdodo/RPC
PHP | 748 lines | 313 code | 94 blank | 341 comment | 23 complexity | cfdb57ebf37d6b378fdd9f7c303e88e5 MD5 | raw file
  1. <?php
  2. /**
  3. * Copyright (c) 2007, Gheorghe Constantin-Adrian. All rights reserved.
  4. *
  5. * Code licensed under the BSD License:
  6. * http://adrian.lastdot.org/license.txt
  7. */
  8. /**
  9. * Base class for all model classes, representing a table
  10. *
  11. * @package Db
  12. */
  13. abstract class RPC_Db_Table_Adapter implements Countable
  14. {
  15. /**
  16. * Represents a insert query
  17. */
  18. const QUERY_INSERT = 'insert';
  19. /**
  20. * Represents a update query
  21. */
  22. const QUERY_UPDATE = 'update';
  23. /**
  24. * Represents a delete query
  25. */
  26. const QUERY_DELETE = 'delete';
  27. /**
  28. * Table's database object
  29. *
  30. * @var RPC_Db_Adapter
  31. */
  32. protected $db = null;
  33. /**
  34. * Table's name
  35. *
  36. * @var string
  37. */
  38. protected $name = '';
  39. /**
  40. * Row object to be returned by load
  41. *
  42. * @var string
  43. */
  44. protected $rowclass = 'RPC_Db_Table_Row';
  45. /**
  46. * Table's primary key column's name
  47. *
  48. * @var string
  49. */
  50. protected $pk = '';
  51. /**
  52. * Array containing the fields
  53. *
  54. * @var array
  55. */
  56. protected $fields = array();
  57. /**
  58. * Array containing the "cleaned" fields. These are without any table
  59. * prefix and with any _ removed
  60. *
  61. * @var array
  62. */
  63. protected $cleanfields = array();
  64. /**
  65. * Identity map for loaded rows from the table
  66. *
  67. * @var RPC_Db_Table_RowMap
  68. */
  69. protected $map = null;
  70. /**
  71. * Finds out what fields the table has and puts them in the $fields array.
  72. * Also the fields are "cleaned" and put in the $cleanfields array, which
  73. * is useful for the getBy* and deleteBy* methods.
  74. */
  75. abstract protected function loadFields();
  76. /**
  77. * Returns an array with all the table's rows
  78. *
  79. * @return array
  80. */
  81. abstract public function getAll();
  82. /**
  83. * Returns all the rows which have the given $field equal to $value
  84. *
  85. * @param string $field
  86. * @param string $value
  87. *
  88. * @return array
  89. */
  90. abstract public function getBy( $field, $value );
  91. /**
  92. * Returns one row (the first in case there are more) which has the given
  93. * $field equal to $value. If no row is found, returns null
  94. *
  95. * @param string $field
  96. * @param string $value
  97. *
  98. * @return RPC_Db_Table_Row
  99. */
  100. abstract public function loadBy( $field, $value );
  101. /**
  102. * Removes the rows which have the $field = $value
  103. *
  104. * @param int $field
  105. * @param mixed $value
  106. *
  107. * @return int Number of affected rows
  108. */
  109. abstract public function deleteBy( $field, $value );
  110. /**
  111. * Performs an insert given the supplied data
  112. *
  113. * @param array $array
  114. *
  115. * @return bool
  116. */
  117. abstract protected function insertRow( RPC_Db_Table_Row $row );
  118. /**
  119. * Performs an update given the supplied data
  120. *
  121. * @param array $array
  122. *
  123. * @return RPC_Db_Table_Row
  124. */
  125. abstract protected function updateRow( RPC_Db_Table_Row $row );
  126. /**
  127. * Deletes all records in a table;
  128. *
  129. * @return int Number of affected rows
  130. */
  131. abstract public function deleteAll();
  132. /**
  133. * Deletes the table and recreates it
  134. *
  135. * @return bool
  136. */
  137. abstract public function truncate();
  138. /**
  139. * Should return the number of rows in the table
  140. *
  141. * @return int
  142. */
  143. abstract public function countRows();
  144. /**
  145. * Checks to see if the given value exists in the current table on the given
  146. * column
  147. *
  148. * @param string $column
  149. * @param string $value
  150. *
  151. * @return bool
  152. */
  153. abstract public function exists( $value, $column );
  154. /**
  155. * Checks to see if a value in a row is unique throught the table
  156. *
  157. * @param string $column Column on which to search
  158. * @param RPC_Db_Table_Row $row Row which holds the value
  159. *
  160. * @return bool
  161. */
  162. abstract public function unique( $column, RPC_Db_Table_Row $row );
  163. /**
  164. * Locks a table
  165. *
  166. * @return bool
  167. */
  168. abstract public function lock();
  169. /**
  170. * Unlocks the table
  171. *
  172. * @return bool
  173. */
  174. abstract public function unlock();
  175. /**
  176. * Initializes the table based on two conventions:
  177. * - object name will be: <table_name>Model
  178. * - table primary key will be: <table_name>_id
  179. */
  180. public function __construct()
  181. {
  182. $this->setDb( RPC_Db::factory() );
  183. if( $this->getName() )
  184. {
  185. $name = $this->getName();
  186. }
  187. else
  188. {
  189. $name = str_replace( 'model', '', strtolower( get_class( $this ) ) );
  190. $this->setName( $this->getDb()->getPrefix() . $name );
  191. }
  192. $this->setPkField( substr( $this->getName(), strlen( $this->getDb()->getPrefix() ) ) . '_id' );
  193. $this->loadFields();
  194. $this->setIdentityMap( new RPC_Db_Table_RowMap() );
  195. }
  196. /**
  197. * Sets the parent database connection
  198. *
  199. * @param RPC_Db_Adapter $database
  200. */
  201. protected function setDb( RPC_Db_Adapter $db )
  202. {
  203. $this->db = $db;
  204. }
  205. /**
  206. * Get the table's database connection
  207. *
  208. * @return RPC_Db_Adapter
  209. */
  210. public function getDb()
  211. {
  212. return $this->db;
  213. }
  214. /**
  215. * Sets the table name
  216. *
  217. * @param string $name
  218. */
  219. public function setName( $name )
  220. {
  221. $this->name = $name;
  222. }
  223. /**
  224. * Get the table's name
  225. *
  226. * @return string
  227. */
  228. public function getName()
  229. {
  230. return $this->name;
  231. }
  232. /**
  233. * Sets an identity map for this table
  234. *
  235. * @param RPC_Db_Table_RowMap $map
  236. */
  237. public function setIdentityMap( RPC_Db_Table_RowMap $map )
  238. {
  239. $this->map = $map;
  240. }
  241. /**
  242. * Returns the table's identity map
  243. *
  244. * @return RPC_Db_Table_RowMap
  245. */
  246. public function getIdentityMap()
  247. {
  248. return $this->map;
  249. }
  250. /**
  251. * Implements Countable interface
  252. *
  253. * @return int
  254. */
  255. public function count()
  256. {
  257. return $this->countRows();
  258. }
  259. /**
  260. * Magic method to allow for cleaner access to the (get|delete|load)By*
  261. * methods
  262. *
  263. * @param string $method
  264. * @param array $args
  265. *
  266. * @return mixed
  267. */
  268. public function __call( $method, $args )
  269. {
  270. if( strpos( $method, 'getBy' ) === 0 )
  271. {
  272. $field = substr( $method, 5 );
  273. return $this->getBy( $field, $args[0] );
  274. }
  275. elseif( strpos( $method, 'deleteBy' ) === 0 )
  276. {
  277. $field = substr( $method, 8 );
  278. return $this->deleteBy( $field, $args[0] );
  279. }
  280. elseif( strpos( $method, 'loadBy' ) === 0 )
  281. {
  282. $field = substr( $method, 6 );
  283. return $this->loadBy( $field, $args[0] );
  284. }
  285. else
  286. {
  287. throw new BadMethodCallException( 'Method "' . $method . '" is not implemented' );
  288. }
  289. }
  290. /**
  291. * Set the table's primary key
  292. *
  293. * @param string $pk
  294. *
  295. * @return RPC_Db_Table_Adapter
  296. */
  297. public function setPkField( $pk )
  298. {
  299. $this->pk = $pk;
  300. return $this;
  301. }
  302. /**
  303. * Get the table's primary key
  304. *
  305. * @return string
  306. */
  307. public function getPkField()
  308. {
  309. return $this->pk;
  310. }
  311. /**
  312. * Create a new, empty row object
  313. *
  314. * @param array $data
  315. *
  316. * @return RPC_Db_Table_Row
  317. */
  318. public function create( $data = array() )
  319. {
  320. if( is_array( $data ) ||
  321. ( is_object( $data ) &&
  322. $data instanceof ArrayAccess ) )
  323. {
  324. /*
  325. We build an array of fields, filling the fields found in the
  326. array with the corresponding values and nulling the rest
  327. */
  328. $tmp = array();
  329. foreach( $this->getFields() as $k => $field )
  330. {
  331. if( isset( $data[$field] ) &&
  332. ! is_array( $data[$field] ) &&
  333. ! is_object( $data[$field] ) )
  334. {
  335. $tmp[$field] = $hash->$field;
  336. }
  337. else
  338. {
  339. $tmp[$field] = null;
  340. }
  341. $tmp[$this->getPkField()] = null;
  342. }
  343. }
  344. else
  345. {
  346. throw new BadArgumentException( 'If given, data must be an array' );
  347. }
  348. return new $this->rowclass( $this, $tmp );
  349. }
  350. /**
  351. * Loads a row from the table or from the identity map if it has already
  352. * been loaded. If no row is found, returns null
  353. *
  354. * @link http://www.martinfowler.com/eaaCatalog/identityMap.html
  355. *
  356. * @param int $pk
  357. *
  358. * @return RPC_Db_Table_Row
  359. */
  360. public function load( $pk )
  361. {
  362. if( ! $pk )
  363. {
  364. return null;
  365. }
  366. /*
  367. First I check to see if the row hasn't already been loaded in the
  368. identity map so that I won't query the database again
  369. */
  370. if( $row = $this->getIdentityMap()->get( $pk ) )
  371. {
  372. return $row;
  373. }
  374. /*
  375. If it hasn't been loaded yet, I will query the database, and if a
  376. row is found it is added to the map
  377. */
  378. $row = $this->loadBy( $this->getPkField(), $pk );
  379. if( is_object( $row ) )
  380. {
  381. $this->getIdentityMap()->add( $row );
  382. }
  383. return $row;
  384. }
  385. /**
  386. * Returns an Row object from an array (the array must have all row
  387. * data, including the pk). This is useful, for example, when selecting
  388. * multiple rows with a query and then creating new objects from each row
  389. *
  390. * @param array $array
  391. *
  392. * @return RPC_Db_Table_Row
  393. */
  394. public function loadFromArray( $array ) /* {{{ */
  395. {
  396. $pk = $array[$this->getPkField()];
  397. if( empty( $pk ) )
  398. {
  399. throw new InvalidArgumentException( 'The array must contain the primary key' );
  400. }
  401. if( $row = $this->getIdentityMap()->get( $pk ) )
  402. {
  403. return $row;
  404. }
  405. $row = new $this->rowclass( $this, $array );
  406. $this->getIdentityMap()->add( $row );
  407. return $row;
  408. }
  409. /* }}} */
  410. /**
  411. * Get the table's fields
  412. *
  413. * @return array
  414. *
  415. * @todo Create a standard value object / structure for fields
  416. */
  417. public function getFields()
  418. {
  419. return $this->fields;
  420. }
  421. /**
  422. * Returns the table's cleaned fields
  423. *
  424. * @return array
  425. */
  426. public function getCleanFields()
  427. {
  428. return $this->cleanfields;
  429. }
  430. /**
  431. * Checks to see if a certain row validates
  432. *
  433. * @param RPC_Db_Table_Row $row
  434. *
  435. * @return bool
  436. */
  437. public function validate( RPC_Db_Table_Row $row )
  438. {
  439. $pk = $row->getPk();
  440. $operation = empty( $pk ) ? RPC_Db::QUERY_INSERT : RPC_Db::QUERY_UPDATE;
  441. if( method_exists( $this, 'preValidate' ) )
  442. {
  443. $this->preValidate( $row, $operation );
  444. }
  445. /**
  446. * For each table field, if a method named validate_fieldname exists it
  447. * is run with the $row and operation type (insert/update)
  448. */
  449. foreach( $this->getCleanFields() as $column => $field )
  450. {
  451. $method = 'validate_' . $column;
  452. if( method_exists( $this, $method ) )
  453. {
  454. $this->$method( $row, $operation );
  455. }
  456. }
  457. if( method_exists( $this, 'postValidate' ) )
  458. {
  459. $this->postValidate( $row, $operation );
  460. }
  461. return ! (bool) $row->hasErrors();
  462. }
  463. /**
  464. * Creates a prepared statement for insertion in the table of a given
  465. * RPC_Db_Table_Row
  466. *
  467. * @param RPC_Db_Table_Row $row
  468. *
  469. * @return bool
  470. */
  471. public function insert( RPC_Db_Table_Row $row )
  472. {
  473. $this->getDb()->beginTransaction();
  474. if( ! $this->onBeforeSave( $row, RPC_Db::QUERY_INSERT ) )
  475. {
  476. $this->getDb()->rollback();
  477. return false;
  478. }
  479. if( ! $this->onBeforeInsert( $row ) )
  480. {
  481. $this->getDb()->rollback();
  482. return false;
  483. }
  484. $this->insertRow( $row );
  485. $pk = $this->getPkField();
  486. $row->setPk( $this->getDb()->getLastId() );
  487. if( ! $this->onAfterInsert( $row ) )
  488. {
  489. $this->getDb()->rollback();
  490. return false;
  491. }
  492. if( ! $this->onAfterSave( $row, RPC_Db::QUERY_INSERT ) )
  493. {
  494. $this->getDb()->rollback();
  495. return false;
  496. }
  497. $this->getDb()->commit();
  498. $this->getIdentityMap()->add( $row );
  499. return true;
  500. }
  501. /**
  502. * Hook called before executing an insert query. If it returns false the
  503. * transaction will be rolled back
  504. *
  505. * @param RPC_Db_Table_Row $row
  506. *
  507. * @return bool
  508. */
  509. public function onBeforeInsert( $row )
  510. {
  511. return true;
  512. }
  513. /**
  514. * Hook called after executing an insert query. If it returns false the
  515. * transaction will be rolled back
  516. *
  517. * @param RPC_Db_Table_Row $row
  518. *
  519. * @return bool
  520. */
  521. public function onAfterInsert( $row )
  522. {
  523. return true;
  524. }
  525. /**
  526. * Creates a prepared statement for update in the table the given row
  527. * identified by it's primary key
  528. *
  529. * @param RPC_Db_Table_Row $row
  530. *
  531. * @return int Number of affected rows
  532. */
  533. public function update( $row )
  534. {
  535. $this->getDb()->beginTransaction();
  536. if( ! $this->onBeforeSave( $row, RPC_Db::QUERY_UPDATE ) )
  537. {
  538. $this->getDb()->rollback();
  539. return false;
  540. }
  541. if( ! $this->onBeforeUpdate( $row ) )
  542. {
  543. $this->getDb()->rollback();
  544. return false;
  545. }
  546. try
  547. {
  548. $this->updateRow( $row );
  549. }
  550. catch( Exception $e )
  551. {
  552. $this->getDb()->rollback();
  553. return false;
  554. }
  555. if( ! $this->onAfterUpdate( $row ) )
  556. {
  557. $this->getDb()->rollback();
  558. return false;
  559. }
  560. if( ! $this->onAfterSave( $row, RPC_Db::QUERY_UPDATE ) )
  561. {
  562. $this->getDb()->rollback();
  563. return false;
  564. }
  565. $this->getDb()->commit();
  566. return true;
  567. }
  568. /**
  569. * Hook called before executing an update query. If it returns false the
  570. * transaction will be rolled back
  571. *
  572. * @param RPC_Db_Table_Row $row
  573. *
  574. * @return bool
  575. */
  576. public function onBeforeUpdate( $row )
  577. {
  578. return true;
  579. }
  580. /**
  581. * Hook called after executing an update query. If it returns false the
  582. * transaction will be rolled back
  583. *
  584. * @param RPC_Db_Table_Row $row
  585. *
  586. * @return bool
  587. */
  588. public function onAfterUpdate( $row )
  589. {
  590. return true;
  591. }
  592. public function onBeforeSave( $row, $op )
  593. {
  594. return true;
  595. }
  596. public function onAfterSave( $row, $op )
  597. {
  598. return true;
  599. }
  600. /**
  601. * Delets the given row
  602. *
  603. * @param RPC_Db_Table_Row $row
  604. *
  605. * @return bool
  606. */
  607. public function delete( $row )
  608. {
  609. $this->getDb()->beginTransaction();
  610. if( ! $this->onBeforeDelete( $row ) )
  611. {
  612. $this->getDb()->rollback();
  613. return false;
  614. }
  615. if( ! (bool) $this->deleteBy( $this->getPkField(), $row->getPk() ) )
  616. {
  617. $this->getDb()->rollback();
  618. return false;
  619. }
  620. if( ! $this->onAfterDelete( $row ) )
  621. {
  622. $this->getDb()->rollback();
  623. return false;
  624. }
  625. $this->getDb()->commit();
  626. $this->getIdentityMap()->remove( $row );
  627. return true;
  628. }
  629. /**
  630. * Hook called before executing a delete query. If it returns false the
  631. * transaction will be rolled back
  632. *
  633. * @param RPC_Db_Table_Row $row
  634. *
  635. * @return bool
  636. */
  637. public function onBeforeDelete( $row )
  638. {
  639. return true;
  640. }
  641. /**
  642. * Hook called after executing a delete query. If it returns false the
  643. * transaction will be rolled back
  644. *
  645. * @param RPC_Db_Table_Row $row
  646. *
  647. * @return bool
  648. */
  649. public function onAfterDelete( $row )
  650. {
  651. return true;
  652. }
  653. }
  654. ?>