PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/MiniMVC/MiniMVC/Table.php

https://github.com/tquensen/MiniMVC
PHP | 586 lines | 350 code | 77 blank | 159 comment | 55 complexity | f2d19221a29760c2402043ef3785d5e8 MD5 | raw file
  1. <?php
  2. class MiniMVC_Table {
  3. /**
  4. * @var PDO
  5. */
  6. protected $_db = null;
  7. protected $connection = null;
  8. /**
  9. * @var MiniMVC_Registry
  10. */
  11. protected $registry = null;
  12. protected $_table = false;
  13. protected $_model = 'MiniMVC_Model';
  14. protected $_columns = array('id');
  15. protected $_relations = array();
  16. protected $_identifier = 'id';
  17. protected $_isAutoIncrement = true;
  18. protected $_returnTypes = array('object' => 'build', 'array' => 'buildArray', 'query' => 'getQueryObject');
  19. public function __construct($connection = null)
  20. {
  21. $this->connection = $connection;
  22. $this->registry = MiniMVC_Registry::getInstance();
  23. $this->_db = $this->registry->db->get($this->connection);
  24. $this->construct();
  25. }
  26. protected function construct()
  27. {
  28. }
  29. /**
  30. *
  31. * @return mixed the column name of the primary key
  32. */
  33. public function getIdentifier()
  34. {
  35. return $this->_identifier;
  36. }
  37. /**
  38. *
  39. * @return PDO the database
  40. */
  41. public function getDb()
  42. {
  43. return $this->_db;
  44. }
  45. /**
  46. *
  47. * @return string
  48. */
  49. public function getConnection()
  50. {
  51. return $this->connection;
  52. }
  53. public function getColumns()
  54. {
  55. return $this->_columns;
  56. }
  57. public function getRelations()
  58. {
  59. return $this->_relations;
  60. }
  61. public function getRelation($relation)
  62. {
  63. return isset($this->_relations[$relation]) ? $this->_relations[$relation] : null;
  64. }
  65. /**
  66. *
  67. * @return string the classname of the model
  68. */
  69. public function getModelName()
  70. {
  71. return $this->_model;
  72. }
  73. /**
  74. *
  75. * @return string the classname of the model
  76. */
  77. public function getTableName()
  78. {
  79. return $this->_table;
  80. }
  81. /**
  82. *
  83. * @return MiniMVC_Collection
  84. */
  85. public function getCollection()
  86. {
  87. if (class_exists($this->_model . 'Collection')) {
  88. $classname = $this->_model . 'Collection';
  89. } else {
  90. $classname = $this->registry->settings->get('config/classes/collection', 'MiniMVC_Collection');
  91. }
  92. return new $classname;
  93. }
  94. /**
  95. *
  96. * @param mixed $id the identifier
  97. * @return MiniMVC_Model
  98. */
  99. public function loadOne($id)
  100. {
  101. return $this->loadOneBy($this->_identifier . ' = ?', $id);
  102. }
  103. /**
  104. * @param string $condition the search condition (? = placeholder)
  105. * @param mixed $value the value(s) for the placeholders in the condition
  106. * @param string $order
  107. * @param int $offset
  108. * @return Mysql_Model
  109. */
  110. public function loadOneBy($condition, $value = null, $order = null, $offset = 0)
  111. {
  112. $result = $this->query()->where($condition)->orderBy($order)->limit(1, $offset)->build($value);
  113. return $result->getFirst();
  114. }
  115. /**
  116. * loads entries with related entries
  117. *
  118. * an relationinfo array looks like:
  119. * array('alias.relation', 'foreignalias', true) //set third parameter to true to select/fetch the related fields
  120. * the alias for the current model is 'a'.
  121. *
  122. * examples:
  123. * 1. array('a.Comments', 'c', true); //in this case, if you use limit or offset, set $usePreQuery to true
  124. * 2. array(array('a.Metadata', 'm', true)array('a.Comments', 'c', true), array('c.User', 'cu', true));
  125. *
  126. * @param mixed $id the identifier
  127. * @param array $relations either one array or a an array containing arrays with relationinfos
  128. * @param string $condition the where condition("id = ?", "a.username LIKE ?")
  129. * @param mixed $values the values for the ?-placeholders
  130. * @param string $order an order by clause (id ASC, foo DESC)
  131. * @param int $offset
  132. * @param bool $needPreQuery set this to true if you use limit or offset with a 1-to-many left join (to limit the resulting entries, not the table rows)
  133. * @return MiniMVC_Model
  134. */
  135. public function loadOneWithRelations($id, $relations = array(), $condition = null, $value = null, $order = null, $offset = 0, $needPreQuery = null)
  136. {
  137. $value = (array) $value;
  138. array_unshift($value, $id);
  139. $results = $this->loadWithRelations($relations, $condition ? 'a.'.$this->_identifier . ' = ? AND '.$condition : 'a.'.$this->_identifier . ' = ?', $value, $order, null, $offset, $needPreQuery);
  140. return $results->getFirst();
  141. }
  142. /**
  143. * loads entries with related entries
  144. *
  145. * an relationinfo array looks like:
  146. * array('alias.relation', 'foreignalias', true) //set third parameter to true to select/fetch the related fields
  147. * the alias for the current model is 'a'.
  148. *
  149. * examples:
  150. * 1. array('a.Comments', 'c', true); //in this case, if you use limit or offset, set $usePreQuery to true
  151. * 2. array(array('a.Metadata', 'm', true)array('a.Comments', 'c', true), array('c.User', 'cu', true));
  152. *
  153. * @param array $relations either one array or a an array containing arrays with relationinfos
  154. * @param string $condition the where condition("id = ?", "a.username LIKE ?")
  155. * @param mixed $values the values for the ?-placeholders
  156. * @param string $order an order by clause (id ASC, foo DESC)
  157. * @param int $offset
  158. * @param bool $needPreQuery set this to true if you use limit or offset with a 1-to-many left join (to limit the resulting entries, not the table rows)
  159. * @return MiniMVC_Model
  160. */
  161. public function loadOneWithRelationsBy($relations = array(), $condition = null, $value = null, $order = null, $offset = 0, $needPreQuery = null)
  162. {
  163. $results = $this->loadWithRelations($relations, $condition, $value, $order, null, $offset, $needPreQuery);
  164. return $results->getFirst();
  165. }
  166. /**
  167. * @param string $condition the where condition("id = ?", "username LIKE ?")
  168. * @param mixed $values the values for the ?-placeholders
  169. * @return bool true if results were found
  170. */
  171. public function exist($condition, $values = null)
  172. {
  173. return (bool) $this->count($condition, $values);
  174. }
  175. /**
  176. *
  177. * @param string $order
  178. * @return MiniMVC_Collection
  179. */
  180. public function loadAll($order = null)
  181. {
  182. return $this->load(null, null, $order);
  183. }
  184. /**
  185. * @param string $condition the where condition("id = ?", "a.username LIKE ?")
  186. * @param mixed $values the values for the ?-placeholders
  187. * @param string $order an order by clause (id ASC, foo DESC)
  188. * @param int $limit
  189. * @param int $offset
  190. * @param string $returnAs the type of data to return (object, array or query)
  191. * @return MiniMVC_Collection
  192. */
  193. public function load($condition = null, $value = null, $order = null, $limit = null, $offset = null, $returnAs = 'object')
  194. {
  195. if (!isset($this->_returnTypes[$returnAs])) {
  196. $returnAs = 'object';
  197. }
  198. return $this->query()->where($condition)->orderBy($order)->limit($limit, $offset)->{$this->_returnTypes[$returnAs]}($value);
  199. }
  200. /**
  201. * loads entries with related entries
  202. *
  203. * an relationinfo array looks like:
  204. * array('alias.relation', 'foreignalias', true) //set third parameter to true to select/fetch the related fields
  205. * the alias for the current model is 'a'.
  206. *
  207. * examples:
  208. * 1. array('a.Comments', 'c', true); //in this case, if you use limit or offset, set $usePreQuery to true
  209. * 2. array(array('a.Metadata', 'm', true)array('a.Comments', 'c', true), array('c.User', 'cu', true));
  210. *
  211. * @param array $relations either one array or a an array containing arrays with relationinfos
  212. * @param string $condition the where condition("id = ?", "a.username LIKE ?")
  213. * @param mixed $values the values for the ?-placeholders
  214. * @param string $order an order by clause (id ASC, foo DESC)
  215. * @param int $limit
  216. * @param int $offset
  217. * @param bool $needPreQuery set this to true if you use limit or offset with a 1-to-many left join (to limit the resulting entries, not the table rows)
  218. * @param string $returnAs the type of data to return (object, array or query)
  219. * @return MiniMVC_Collection
  220. */
  221. public function loadWithRelations($relations = array(), $condition = null, $value = null, $order = null, $limit = null, $offset = null, $needPreQuery = null, $returnAs = 'object')
  222. {
  223. if (!isset($this->_returnTypes[$returnAs])) {
  224. $returnAs = 'object';
  225. }
  226. $q = $this->query('a');
  227. foreach ($relations as $relation) {
  228. if (!is_array($relation)) {
  229. if (!empty($relations[0]) && !empty($relations[1])) {
  230. if ((!empty($relations[2]) && $relations[2] === true) || (!empty($relations[3]) && $relations[3] === true)) {
  231. $q->select($relations[1]);
  232. } elseif (!empty($relations['select'])) {
  233. $q->select($relations['select']);
  234. }
  235. $q->join($relations[0], $relations[1], !empty($relations[2]) && $relations[2] !== true ? $relations[2] : null, !empty($relations[3]) && $relations[3] !== true ? $relations[3] : 'LEFT');
  236. }
  237. break;
  238. }
  239. if (!empty($relation[0]) && !empty($relation[1])) {
  240. if ((!empty($relation[2]) && $relation[2] === true) || (!empty($relation[3]) && $relation[3] === true)) {
  241. $q->select($relation[1]);
  242. } elseif (!empty($relation['select'])) {
  243. $q->select($relation['select']);
  244. }
  245. $q->join($relation[0], $relation[1], !empty($relation[2]) && $relation[2] !== true ? $relation[2] : null, !empty($relation[3]) && $relation[3] !== true ? $relation[3] : 'LEFT');
  246. }
  247. }
  248. return $q->where($condition)->orderBy($order)->limit($limit, $offset, $needPreQuery)->{$this->_returnTypes[$returnAs]}($value);
  249. }
  250. /**
  251. * @param string $condition the where condition("id = ?", "username LIKE ?")
  252. * @param mixed $values the values for the ?-placeholders
  253. * @return int num results found
  254. */
  255. public function count($condition = null, $values = null)
  256. {
  257. if (!is_array($values) && $values !== null) {
  258. $values = array($values);
  259. }
  260. if ($stmt = $this->query(null, 'COUNT(*)')->where($condition)->execute($values)) {
  261. return $stmt->fetchColumn();
  262. }
  263. return false;
  264. }
  265. /**
  266. *
  267. * @param string $alias the alias of this model
  268. * @param bool $select true to add a $query->select($alias)
  269. * @return MiniMVC_Query
  270. */
  271. public function query($alias = null, $select = true)
  272. {
  273. $q = $this->registry->db->query($this->connection);
  274. if ($select === true) {
  275. $q->select($alias);
  276. } elseif($select) {
  277. $q->select($select);
  278. }
  279. return $q->from($this, $alias);
  280. }
  281. public function buildModel($row)
  282. {
  283. if (empty($row) || !isset($row[$this->_identifier])) {
  284. return null;
  285. }
  286. $entry = new $this->_model($this);
  287. foreach ($row as $k=>$v)
  288. {
  289. $entry->$k = $v;
  290. if (in_array($k, $this->_columns)) {
  291. $entry->setDatabaseProperty($k, $v);
  292. }
  293. }
  294. $entry->postLoad();
  295. return $entry;
  296. }
  297. public function create($data = array())
  298. {
  299. $entry = new $this->_model($this);
  300. foreach ($data as $k=>$v)
  301. {
  302. $entry->$k = $v;
  303. }
  304. $entry->postCreate();
  305. return $entry;
  306. }
  307. public function save($entry)
  308. {
  309. try
  310. {
  311. $this->_db->beginTransaction();
  312. if ($entry->preSave() === false) {
  313. $this->_db->rollBack();
  314. return false;
  315. }
  316. if ($entry->isNew()) {
  317. if ($entry->preInsert() === false) {
  318. $this->_db->rollBack();
  319. return false;
  320. }
  321. $fields = array();
  322. $values = array();
  323. foreach ($this->_columns as $column)
  324. {
  325. if (isset($entry->$column) && $entry->$column !== null)
  326. {
  327. $fields[] = $column;
  328. //$query->set(' '.$column.' = ? ');
  329. $values[] = $entry->$column;
  330. }
  331. }
  332. $query = $this->query()->insert($fields, $values);
  333. $result = $query->execute();
  334. if ($this->_isAutoIncrement)
  335. {
  336. $entry->{$this->_identifier} = $this->_db->lastInsertId();
  337. }
  338. foreach ($this->_columns as $column)
  339. {
  340. $entry->setDatabaseProperty($column, $entry->$column);
  341. }
  342. if ($entry->postInsert() === false) {
  343. $this->_db->rollBack();
  344. return false;
  345. }
  346. } else {
  347. $update = false;
  348. if ($entry->preUpdate() === false) {
  349. $this->_db->rollBack();
  350. return false;
  351. }
  352. $fields = array();
  353. $values = array();
  354. foreach ($this->_columns as $column)
  355. {
  356. if ($entry->$column !== $entry->getDatabaseProperty($column))
  357. {
  358. $fields[] = $column;
  359. //$query->set(' '.$column.' = ? ');
  360. $values[] = $entry->$column;
  361. $update = true;
  362. }
  363. }
  364. $query = $this->query()->update($fields)->where($this->_identifier.' = ?');
  365. if (!$update) {
  366. $this->_db->rollBack();
  367. return true;
  368. }
  369. $values[] = $entry->{$this->_identifier};
  370. $result = $query->execute($values);
  371. foreach ($this->_columns as $column)
  372. {
  373. if (isset($entry->$column) && $entry->$column !== $entry->getDatabaseProperty($column))
  374. {
  375. $entry->setDatabaseProperty($column, $entry->$column);
  376. }
  377. }
  378. if ($entry->postUpdate() === false) {
  379. $this->_db->rollBack();
  380. return false;
  381. }
  382. }
  383. if ($entry->postSave() === false) {
  384. $this->_db->rollBack();
  385. return false;
  386. }
  387. $this->_db->commit();
  388. } catch (Exception $e) {
  389. $this->_db->rollBack();
  390. throw $e;
  391. }
  392. return (bool) $result;
  393. }
  394. public function delete($entry)
  395. {
  396. try
  397. {
  398. $this->_db->beginTransaction();
  399. if (is_object($entry))
  400. {
  401. if (!isset($entry->{$this->_identifier}) || !$entry->{$this->_identifier})
  402. {
  403. $this->_db->rollBack();
  404. return false;
  405. }
  406. if ($entry->preDelete() === false) {
  407. $this->_db->rollBack();
  408. return false;
  409. }
  410. $query = $this->registry->db->query();
  411. $result = $query->delete($this)->where($this->_identifier.' = ?')->limit(1)->execute($entry->{$this->_identifier});
  412. $entry->clearDatabaseProperties();
  413. if ($entry->postDelete() === false) {
  414. $this->_db->rollBack();
  415. return false;
  416. }
  417. }
  418. else
  419. {
  420. $query = $this->registry->db->query();
  421. $result = $query->delete($this)->where($this->_identifier.' = ?')->limit(1)->execute($entry);
  422. }
  423. foreach ($this->_relations as $relation => $info) {
  424. if (isset($info[3]) && $info[3] !== true) {
  425. $this->registry->db->query()->delete($info[3])->where($info[1].' = ?')->execute(is_object($entry) ? $entry->{$this->_identifier} : $entry);
  426. }
  427. }
  428. $this->_db->commit();
  429. } catch (Exception $e) {
  430. $this->_db->rollBack();
  431. throw $e;
  432. }
  433. return $result;
  434. }
  435. public function deleteBy($condition, $values, $cleanRefTable = false)
  436. {
  437. $query = $this->registry->db->query();
  438. $result = $query->delete($this)->where($condition)->execute($values);
  439. if ($cleanRefTable) {
  440. $this->cleanRefTables();
  441. }
  442. return $result;
  443. }
  444. /**
  445. * deletes all rows in m:n ref tables which have no related entry in this class
  446. */
  447. public function cleanRefTables()
  448. {
  449. foreach ($this->_relations as $relation => $info) {
  450. if (!isset($info[3]) || $info[3] === true) {
  451. continue;
  452. }
  453. $stmt = $this->registry->db->query()->select('a_b.id')->from($info[3], 'a_b')->join($this->_table, 'a', 'a_b.'.$info[1].' = a.'.$this->_identifier)->where('a.'.$this->_identifier.' IS NULL')->execute();
  454. $refTableIds = $stmt->fetchAll(PDO::FETCH_COLUMN);
  455. $deleteStmt = $this->registry->db->query()->delete($info[3])->where('id = ?')->prepare();
  456. foreach ($refTableIds as $refTableId) {
  457. $deleteStmt->execute(array($refTableId));
  458. }
  459. }
  460. }
  461. // public function orderBy($field, $direction, $entries)
  462. // {
  463. // $dir = (strtolower($direction) == 'asc') ? SORT_ASC : SORT_DESC;
  464. // $fields = array();
  465. // foreach ($entries as $key => $row) {
  466. // if (!isset($row->$field))
  467. // {
  468. // return array();
  469. // }
  470. // $fields[$key] = $row->$field;
  471. // }
  472. //
  473. //
  474. // array_multisort($fields, $dir, $entries);
  475. //
  476. // $newEntries = array();
  477. // foreach ($entries as $entry)
  478. // {
  479. // $newEntries[$entry->{$this->_identifier}] = $entry;
  480. // }
  481. // return $newEntries;
  482. // }
  483. public function install()
  484. {
  485. }
  486. public function uninstall()
  487. {
  488. }
  489. public function generateSlug($entry, $source, $field, $maxlength = 255)
  490. {
  491. $baseslug = $this->registry->helper->text->sanitize($source, true);
  492. $id = $entry->getIdentifier() ? $entry->getIdentifier() : 0;
  493. $sql = 'SELECT count(*) FROM '.$this->_table.' WHERE '.$this->_identifier.' != ? and '.$field.' = ?';
  494. $stmt = $this->_db->prepare($sql);
  495. $num = 0;
  496. $slug = $baseslug;
  497. do {
  498. if (mb_strlen($slug, 'UTF-8') > $maxlength) {
  499. $baseslug = mb_substr($baseslug, 0, $maxlength - strlen((string) $num), 'UTF-8');
  500. $slug = $baseslug . $num;
  501. }
  502. $stmt->execute(array($id, $slug));
  503. $num--;
  504. $result = $stmt->fetchColumn();
  505. $stmt->closeCursor();
  506. } while($result && $slug = $baseslug . $num);
  507. return $slug;
  508. }
  509. }