PageRenderTime 69ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/library/Centurion/Db/Table/Row/Abstract.php

http://github.com/centurion-project/Centurion
PHP | 1625 lines | 983 code | 257 blank | 385 comment | 214 complexity | 2c8094e928c629042ae1febe6859f10d MD5 | raw file
Possible License(s): BSD-3-Clause

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * Centurion
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * If you did not receive a copy of the license and are unable to
  10. * obtain it through the world-wide-web, please send an email
  11. * to license @centurion-project.org so we can send you a copy immediately.
  12. *
  13. * @category Centurion
  14. * @package Centurion_Db
  15. * @subpackage Table
  16. * @copyright Copyright (c) 2008-2011 Octave & Octave (http://www.octaveoctave.com)
  17. * @license http://centurion-project.org/license/new-bsd New BSD License
  18. * @version $Id$
  19. */
  20. /**
  21. * @category Centurion
  22. * @package Centurion_Db
  23. * @subpackage Table
  24. * @copyright Copyright (c) 2008-2011 Octave & Octave (http://www.octaveoctave.com)
  25. * @license http://centurion-project.org/license/new-bsd New BSD License
  26. * @author Florent Messa <florent.messa@gmail.com>
  27. * @author Laurent Chenay <lc@centurion-project.org>
  28. */
  29. abstract class Centurion_Db_Table_Row_Abstract extends Zend_Db_Table_Row_Abstract implements Centurion_Traits_Traitsable
  30. {
  31. protected $_specialGets = array(
  32. 'permalink' => '_getAbsoluteUrl',
  33. 'admin_permalink' => '_getAdminUrl',
  34. 'pk' => 'getPrimaryKey',
  35. 'px' => 'getPx'
  36. );
  37. /**
  38. * References to parent tables.
  39. *
  40. * @var $_parents Centurion_Db_Table_Row_Abstract
  41. */
  42. protected $_parents = array();
  43. /**
  44. * Children, dependent tables and Many to Many constraints.
  45. *
  46. * @var Centurion_Db_Table_Row_Abstract
  47. */
  48. protected $_children = array();
  49. /**
  50. * Children by reference map.
  51. *
  52. * @var array[string][string]Centurion_Db_Table_Row_Abstract
  53. */
  54. protected static $_relationship = array();
  55. /**
  56. * @var array
  57. * @todo Is it still used ? Trait proxy ?
  58. */
  59. protected $_proxyRow = null;
  60. /**
  61. * @var unknown_type
  62. */
  63. protected $_cache = null;
  64. protected $_traitQueue;
  65. public function getTraitQueue()
  66. {
  67. if (null == $this->_traitQueue) {
  68. $this->_traitQueue = new Centurion_Traits_Queue();
  69. }
  70. return $this->_traitQueue;
  71. }
  72. public function __clone()
  73. {
  74. $this->_traitQueue = null;
  75. Centurion_Traits_Common::initTraits($this);
  76. }
  77. public function __sleep()
  78. {
  79. return array_merge(array('_specialGets', '_traitQueue'), parent::__sleep());
  80. }
  81. public function __construct(array $config)
  82. {
  83. parent::__construct($config);
  84. Centurion_Traits_Common::initTraits($this);
  85. }
  86. /**
  87. * Set row field value
  88. *
  89. * @param string $columnName The column key
  90. * @param mixed $value The value for the property
  91. * @return void
  92. * @throws Centurion_Db_Table_Exception
  93. */
  94. public function __set($columnName, $value)
  95. {
  96. $columnName = $this->_transformColumn($columnName);
  97. if (!array_key_exists($columnName, array_merge($this->_data, $this->_specialGets))) {
  98. throw new Centurion_Db_Table_Exception(sprintf("Specified column \"%s\" is not in the row", $columnName));
  99. }
  100. $method = 'set' . ucfirst($columnName);
  101. if (method_exists($this, $method)) {
  102. call_user_func(array($this, $method), $value);
  103. } else {
  104. $this->_data[$columnName] = $value;
  105. }
  106. $this->_modifiedFields[$columnName] = true;
  107. // @todo implement set for trait
  108. }
  109. /**
  110. * Magic method to get relationship.
  111. *
  112. * @param string $columnName Column name.
  113. * @return Centurion_Db_Table_Row_Abstract|Centurion_Db_Table_Rowset_Abstract
  114. */
  115. public function __get($columnName)
  116. {
  117. $columnName = $this->_transformColumn($columnName);
  118. if (array_key_exists($columnName, $this->_specialGets)) {
  119. if (is_string($this->_specialGets[$columnName])) {
  120. if (!method_exists($this, $this->_specialGets[$columnName])) {
  121. throw new Zend_Db_Table_Row_Exception(sprintf("Specified method \"%s\" does not exist", $this->_specialGets[$columnName]));
  122. } else {
  123. return call_user_func(array($this, $this->_specialGets[$columnName]));
  124. }
  125. } elseif (is_array($this->_specialGets[$columnName]) && 2 == count($this->_specialGets[$columnName])) {
  126. if (is_object($this->_specialGets[$columnName][0]) && is_string($this->_specialGets[$columnName][1]) && method_exists($this->_specialGets[$columnName][0], $this->_specialGets[$columnName][1])) {
  127. return call_user_func_array($this->_specialGets[$columnName], array());
  128. } else if (!method_exists($this, (string) $this->_specialGets[$columnName][0])) {
  129. throw new Zend_Db_Table_Row_Exception(sprintf("Specified method \"%s\" does not exist", $this->_specialGets[$columnName][0]));
  130. } else {
  131. return call_user_func_array(array(
  132. $this,
  133. $this->_specialGets[$columnName][0]
  134. ), (array) $this->_specialGets[$columnName][1]);
  135. }
  136. }
  137. }
  138. if (array_key_exists($columnName, $this->_data)) {
  139. return $this->_getRawData($columnName);
  140. }
  141. //TODO: cascade object__attribute__object__atribute...
  142. if (null !== ($pos = strpos($columnName, '+')) && $pos !== false) {
  143. $part1 = $this->{trim(substr($columnName, 0, $pos))};
  144. $part2 = $this->{trim(substr($columnName, $pos + 1))};
  145. return $part1 . '' . $part2;
  146. }
  147. if (null !== ($pos = strpos($columnName, '__')) && $pos !== false) {
  148. $row = $this->{substr($columnName, 0, $pos)};
  149. if ($row == null) {
  150. return null;
  151. }
  152. return $row->{substr($columnName, $pos + 2)};
  153. }
  154. $referenceMap = $this->getTable()->info('referenceMap');
  155. if (isset($referenceMap[$columnName])) {
  156. $columns = $referenceMap[$columnName]['columns'];
  157. $className = $referenceMap[$columnName]['refTableClass'];
  158. if (is_string($columns)) {
  159. $pkValue = $this->{$columns};
  160. } else {
  161. $pkValue = array();
  162. foreach ($columns as $column) {
  163. $pkValue[] = $this->$column;
  164. }
  165. $pkValue = md5(implode('___', $pkValue));
  166. }
  167. if (!isset(self::$_relationship[$className][$pkValue])) {
  168. self::$_relationship[$className][$pkValue]
  169. = $this->findParentRow($referenceMap[$columnName]['refTableClass'],
  170. $columnName);
  171. if (null === self::$_relationship[$className][$pkValue]) {
  172. self::$_relationship[$className][$pkValue] = false;
  173. }
  174. }
  175. if (false == self::$_relationship[$className][$pkValue]) {
  176. return null;
  177. }
  178. return self::$_relationship[$className][$pkValue];
  179. }
  180. $dependentTables = $this->getTable()->info('dependentTables');
  181. if (isset($dependentTables[$columnName])) {
  182. if (!isset($this->_children[$columnName])) {
  183. $this->_children[$columnName] = $this->findDependentRowset($dependentTables[$columnName]);
  184. }
  185. return $this->_children[$columnName];
  186. }
  187. $manyDependentTables = $this->getTable()->info('manyDependentTables');
  188. if (isset($manyDependentTables[$columnName])) {
  189. if (!isset($this->_children[$columnName])) {
  190. $data = $manyDependentTables[$columnName];
  191. Centurion_Db_Table_Abstract::setFiltersStatus(true);
  192. $select = Centurion_Db::getSingletonByClassName($data['refTableClass'])->select();
  193. $refForeignCond = null;
  194. if (isset($data['refforeigncond'])) {
  195. $refForeignCond = $data['refforeigncond'];
  196. }
  197. //TODO: this should be remove after. It's only for retrocompatibility
  198. if (!isset($data['refforeign']) && isset($data['columns'])) {
  199. $data['refforeign'] = substr($data['columns']['foreign'], 0, -3);
  200. $data['reflocal'] = substr($data['columns']['local'], 0, -3);
  201. }
  202. //Support of ordered many to many to avoid regression
  203. $intersectionTable = $data['intersectionTable'];
  204. if (is_string($intersectionTable)) {
  205. //this operation is also performed in the next call to findManyToManyRowset()
  206. $intersectionTable = $this->_getTableFromString($intersectionTable);
  207. }
  208. if (in_array('order', $intersectionTable->info('cols'))) {
  209. // Use adapter from intersection table to ensure correct query construction
  210. $interName = $intersectionTable->info(Centurion_Db_Table::NAME);
  211. //findManyToManyRowset() not perform this because we send a select object to the method
  212. $select->order($interName.'.order asc');
  213. }
  214. $this->_children[$columnName]
  215. = $this->findManyToManyRowset($data['refTableClass'],
  216. $intersectionTable,
  217. $data['reflocal'],
  218. $data['refforeign'],
  219. $select, $refForeignCond);
  220. //todo: fix this
  221. //$this->_children[$columnName]->setIntersectionColumns($manyDependentTables[$extractedColumnName]['columns']);
  222. Centurion_Db_Table_Abstract::restoreFiltersStatus();
  223. }
  224. return $this->_children[$columnName];
  225. }
  226. throw new Zend_Db_Table_Row_Exception("Specified column \"$columnName\" is not in the row");
  227. }
  228. public function toArray()
  229. {
  230. $return = array();
  231. foreach ($this->_data as $key => $val) {
  232. $return[$key] = $this->_getRawData($key);
  233. }
  234. return $return;
  235. }
  236. /**
  237. * @return array
  238. */
  239. public function getCleanData()
  240. {
  241. return $this->_cleanData;
  242. }
  243. public function getTable()
  244. {
  245. if (null === $this->_table && null !== $this->_tableClass) {
  246. $this->_table = $this->_getTableFromString($this->_tableClass);
  247. $this->_connected = true;
  248. }
  249. return $this->_table;
  250. }
  251. /**
  252. * Query a parent table to retrieve the single row matching the current row.
  253. *
  254. * @param string|Zend_Db_Table_Abstract $parentTable
  255. * @param string OPTIONAL $ruleKey
  256. * @param Zend_Db_Table_Select OPTIONAL $select
  257. * @return Zend_Db_Table_Row_Abstract Query result from $parentTable
  258. * @throws Zend_Db_Table_Row_Exception If $parentTable is not a table or is not loadable.
  259. */
  260. public function findParentRow($parentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
  261. {
  262. $db = $this->_getTable()->getAdapter();
  263. if (is_string($parentTable)) {
  264. $parentTable = $this->_getTableFromString($parentTable);
  265. }
  266. if (!$parentTable instanceof Zend_Db_Table_Abstract) {
  267. $type = gettype($parentTable);
  268. if ($type == 'object') {
  269. $type = get_class($parentTable);
  270. }
  271. throw new Zend_Db_Table_Row_Exception("Parent table must be a Zend_Db_Table_Abstract, but it is $type");
  272. }
  273. // even if we are interacting between a table defined in a class and a
  274. // table via extension, ensure to persist the definition
  275. if (($tableDefinition = $this->_table->getDefinition()) !== null
  276. && ($parentTable->getDefinition() == null)
  277. ) {
  278. $parentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  279. }
  280. if ($select === null) {
  281. $select = $parentTable->select();
  282. } else {
  283. $select->setTable($parentTable);
  284. }
  285. $map = $this->_prepareReference($this->_getTable(), $parentTable, $ruleKey);
  286. // iterate the map, creating the proper wheres
  287. for ($i = 0 ; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]) ; ++$i) {
  288. $dependentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
  289. $value = $this->{$dependentColumnName};
  290. // Use adapter from parent table to ensure correct query construction
  291. $parentDb = $parentTable->getAdapter();
  292. $parentColumnName = $parentDb->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  293. $parentColumn = $parentDb->quoteIdentifier($parentColumnName, true);
  294. $parentInfo = $parentTable->info();
  295. // determine where part
  296. $type = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['DATA_TYPE'];
  297. $nullable = $parentInfo[Zend_Db_Table_Abstract::METADATA][$parentColumnName]['NULLABLE'];
  298. if ($value === null && $nullable == true) {
  299. $select->where("$parentColumn IS NULL");
  300. } elseif ($value === null && $nullable == false) {
  301. return null;
  302. } else {
  303. $select->where($parentInfo['name'] . '.' . "$parentColumn = ?", $value, $type);
  304. }
  305. }
  306. return $parentTable->fetchRow($select);
  307. }
  308. /**
  309. * Test existence of row field
  310. *
  311. * @param string $columnName The column key.
  312. * @return boolean
  313. */
  314. public function __isset($columnName)
  315. {
  316. $columnName = $this->_transformColumn($columnName);
  317. //Test direct column
  318. if ($this->columnsExists($columnName)) {
  319. return true;
  320. }
  321. //Test concatenation of column
  322. if (null !== ($pos = strpos($columnName, '+')) && $pos !== false) {
  323. $part1 = isset($this->{trim(substr($columnName, 0, $pos))});
  324. $part2 = isset($this->{trim(substr($columnName, $pos + 1))});
  325. return $part1 && $part2;
  326. }
  327. //Test reference, dependant and many dependant column with cascade
  328. if (null !== ($pos = strpos($columnName, '__')) && $pos !== false) {
  329. $row = $this->{substr($columnName, 0, $pos)};
  330. if ($row == null) {
  331. return false;
  332. }
  333. return isset($row->{substr($columnName, $pos + 2)});
  334. }
  335. //Test special get column
  336. if (array_key_exists($columnName, $this->_specialGets)) {
  337. $callbackValue = $this->_specialGets[$columnName];
  338. if(is_string($callbackValue)) {
  339. $callbackValue = array($this, $callbackValue);
  340. }
  341. if(!is_callable($callbackValue)) {
  342. throw new Centurion_Db_Table_Exception(sprintf("Specified callback uncallable for %s", $columnName));
  343. }
  344. return true;
  345. }
  346. $referenceMap = $this->getTable()->info('referenceMap');
  347. if (isset($referenceMap[$columnName])) {
  348. return true;
  349. }
  350. $dependentTables = $this->getTable()->info('dependentTables');
  351. if (isset($dependentTables[$columnName])) {
  352. return true;
  353. }
  354. $manyDependentTables = $this->getTable()->info('manyDependentTables');
  355. if (isset($manyDependentTables[$columnName])) {
  356. return true;
  357. }
  358. return parent::__isset($columnName);
  359. }
  360. /**
  361. * @param string $columnName the column to test
  362. * @return bool True if column exist in the row
  363. */
  364. public function columnsExists($columnName)
  365. {
  366. return array_key_exists($columnName, $this->_data);
  367. }
  368. protected function _getRawData($columnName)
  369. {
  370. if ($this->columnsExists($columnName)) {
  371. list($found, $retVal) = Centurion_Traits_Common::checkTraitOverload($this, __METHOD__, $columnName);
  372. if (!$found) {
  373. return $this->_data[$columnName];
  374. } else {
  375. return $retVal;
  376. }
  377. } else {
  378. throw new Exception(sprintf('Column %s doesn\'t exists', $columnName));
  379. }
  380. }
  381. public function isAllowedContext($context, $ressource = null)
  382. {
  383. return in_array($context, iterator_to_array($this->_traitQueue), true);
  384. }
  385. /**
  386. * Return if current row is new (and so will be inserted and not updated)
  387. *
  388. * @return true|false
  389. */
  390. public function isNew()
  391. {
  392. return empty($this->_cleanData);
  393. }
  394. public function delegateGet($context, $column)
  395. {
  396. if (!$this->isAllowedContext($context, $column)) {
  397. throw new Centurion_Db_Exception(sprintf('Unauthorize property %s', $column));
  398. }
  399. return $this->$column;
  400. }
  401. public function delegateSet($context, $column, $value)
  402. {
  403. if (!$this->isAllowedContext($context, $column)) {
  404. throw new Centurion_Db_Exception(sprintf('Unauthorize property %s', $column));
  405. }
  406. $this->$column = $value;
  407. }
  408. public function delegateCall($context, $method, $args = array())
  409. {
  410. if (!$this->isAllowedContext($context, $method)) {
  411. throw new Centurion_Db_Exception(sprintf('Unauthorize method %s', $method));
  412. }
  413. return call_user_func_array(array($this, $method), $args);
  414. }
  415. public function __call($method, array $args)
  416. {
  417. $lcMethod = strtolower($method);
  418. //TODO: change all substr by strncmp
  419. if (!strncmp($lcMethod, 'getnextby', 9)) {
  420. $by = substr($method, 9, strlen($method));
  421. $method = 'getNextBy';
  422. } else if (substr($lcMethod, 0, 13) == 'getpreviousby') {
  423. $by = substr($method, 13, strlen($method));
  424. $method = 'getPreviousBy';
  425. } else if (substr($lcMethod, 0, 10) == 'getfirstby') {
  426. $by = substr($method, 10, strlen($method));
  427. $method = 'getFirstBy';
  428. } else if (substr($lcMethod, 0, 9) == 'getlastby') {
  429. $by = substr($method, 9, strlen($method));
  430. $method = 'getLastBy';
  431. } else if (substr($lcMethod, 0, 15) == 'getdateobjectby') {
  432. $by = substr($method, 15, strlen($method));
  433. $method = 'getDateObjectBy';
  434. } else if (substr($lcMethod, 0, 16) == 'getpictureorpxby') {
  435. $by = substr($method, 16, strlen($method));
  436. $method = 'getPictureOrPxBy';
  437. } else if (substr($lcMethod, 0, 3) == 'get' && preg_match('`get((.+)Or(.+))+`', $method)) {
  438. $lcMethod = substr($lcMethod, 3);
  439. $columns = explode('or', $lcMethod);
  440. foreach ($columns as $column) {
  441. if (isset($this->$column) && null !== $this->$column) {
  442. return $this->$column;
  443. }
  444. }
  445. return null;
  446. }
  447. if (isset($by)) {
  448. return call_user_func_array(array($this, $method), array_merge(array($by), $args));
  449. }
  450. if (is_array($args) && count($args) == 1 && $args[0] instanceof Centurion_Db_Table_Select) {
  451. $columnName = $this->_transformColumn($method);
  452. $referenceMap = $this->getTable()->info('referenceMap');
  453. $select = $args[0];
  454. if (isset($referenceMap[$columnName])) {
  455. $column = $referenceMap[$columnName]['columns'];
  456. $className = $referenceMap[$columnName]['refTableClass'];
  457. if (!isset(self::$_relationship[$className][$this->{$column}])) {
  458. self::$_relationship[$className][$this->{$column}]
  459. = $this->findParentRow($referenceMap[$columnName]['refTableClass'], $columnName, $select);
  460. if (null === self::$_relationship[$className][$this->{$column}]) {
  461. self::$_relationship[$className][$this->{$column}] = false;
  462. }
  463. }
  464. if (false == self::$_relationship[$className][$this->{$column}]) {
  465. return null;
  466. }
  467. return self::$_relationship[$className][$this->{$column}];
  468. }
  469. $dependentTables = $this->getTable()->info('dependentTables');
  470. if (isset($dependentTables[$columnName])) {
  471. if (!isset($this->_children[$columnName])) {
  472. $this->_children[$columnName] = $this->findDependentRowset($dependentTables[$columnName], null, $select);
  473. }
  474. return $this->_children[$columnName];
  475. }
  476. $manyDependentTables = $this->getTable()->info('manyDependentTables');
  477. if (isset($manyDependentTables[$columnName])) {
  478. if (!isset($this->_children[$columnName])) {
  479. $this->_children[$columnName] = $this->findManyToManyRowset($manyDependentTables[$columnName]['refTableClass'],
  480. $manyDependentTables[$columnName]['intersectionTable'],
  481. null, null, $select);
  482. $this->_children[$columnName]->setIntersectionColumns($manyDependentTables[$columnName]['columns']);
  483. }
  484. return $this->_children[$columnName];
  485. }
  486. }
  487. try {
  488. $retVal = parent::__call($method, $args);
  489. } catch (Zend_Db_Table_Row_Exception $e) {
  490. list($found, $retVal) = Centurion_Traits_Common::checkTraitOverload($this, $method, $args);
  491. if (!$found) {
  492. throw $e;
  493. }
  494. }
  495. return $retVal;
  496. }
  497. public function init()
  498. {
  499. $this->_hydrateData($this->_data);
  500. parent::init();
  501. }
  502. protected function _hydrateData($data)
  503. {
  504. $referenceData = array();
  505. $lenSeparator = strlen(Centurion_Db_Table_Select::HYDRATE_SEPARATOR);
  506. $referenceMap = $this->getTable()->info('referenceMap');
  507. foreach ($data as $key => $cols) {
  508. if ($pos = strpos($key, Centurion_Db_Table_Select::HYDRATE_SEPARATOR)) {
  509. $reference = substr($key, 0, $pos);
  510. $referenceKey = substr($key, $pos + $lenSeparator);
  511. if (!isset($referenceMap[$reference])) {
  512. continue;
  513. }
  514. if (!isset($referenceData[$reference])) {
  515. $referenceData[$reference] = array();
  516. }
  517. $referenceData[$reference][$referenceKey] = $cols;
  518. }
  519. }
  520. foreach ($referenceData as $reference => $cols) {
  521. $column = $referenceMap[$reference]['columns'];
  522. $className = $referenceMap[$reference]['refTableClass'];
  523. //We dont hydrate, if all column are null (hydrate using left join)
  524. $useIt = false;
  525. foreach ($cols as $col) {
  526. if (null !== $col) {
  527. $useIt = true;
  528. break;
  529. }
  530. }
  531. if (!$useIt) {
  532. continue;
  533. }
  534. if (!isset(self::$_relationship[$className][$this->{$column}])) {
  535. $table = Centurion_Db::getSingletonByClassName($className);
  536. $rowData = array();
  537. $rowData['table'] = $table;
  538. $rowData['data'] = $cols;
  539. $rowData['stored'] = true;
  540. $rowClassName = $table->getRowClass();
  541. $exist = true;
  542. foreach($table->info('primary') as $pkfield) {
  543. if(empty($rowData['data'][$pkfield])) {
  544. $exist = false;
  545. break;
  546. }
  547. }
  548. if(!$exist) {
  549. self::$_relationship[$className][$this->{$column}] = false;
  550. }
  551. else {
  552. self::$_relationship[$className][$this->{$column}] = new $rowClassName($rowData);
  553. }
  554. }
  555. }
  556. }
  557. public function getCache($frontendOptions = null, $backendOptions = null)
  558. {
  559. if (null === $this->_cache) {
  560. if (null === $frontendOptions) {
  561. $frontendOptions = Centurion_Db_Table_Abstract::getDefaultFrontendOptions();
  562. }
  563. if (null === $backendOptions) {
  564. $backendOptions = Centurion_Db_Table_Abstract::getDefaultBackendOptions();
  565. }
  566. $frontendOptions['cache_id_prefix'] = get_class($this) . '_' . md5(serialize($this->pk)) . '_';
  567. $this->_cache = new Centurion_Db_Cache($this, $frontendOptions, $backendOptions);
  568. }
  569. return $this->_cache;
  570. }
  571. /**
  572. * @param string|Zend_Db_Table_Abstract $matchTable
  573. * @param string|Zend_Db_Table_Abstract $intersectionTable
  574. * @param string OPTIONAL $callerRefRule
  575. * @param string OPTIONAL $matchRefRule
  576. * @param Zend_Db_Table_Select OPTIONAL $select
  577. * @return Zend_Db_Table_Rowset_Abstract Query result from $matchTable
  578. * @throws Zend_Db_Table_Row_Exception If $matchTable or $intersectionTable is not a table class or is not loadable.
  579. */
  580. public function findManyToManyRowset($matchTable, $intersectionTable, $callerRefRule = null,
  581. $matchRefRule = null, Zend_Db_Table_Select $select = null)
  582. {
  583. if (is_string($matchTable)) {
  584. $matchTable = $this->_getTableFromString($matchTable);
  585. }
  586. if (is_string($intersectionTable)) {
  587. $intersectionTable = $this->_getTableFromString($intersectionTable);
  588. }
  589. // Use adapter from intersection table to ensure correct query construction
  590. $interInfo = $intersectionTable->info();
  591. $interDb = $intersectionTable->getAdapter();
  592. $interName = $interInfo['name'];
  593. $interSchema = isset($interInfo['schema']) ? $interInfo['schema'] : null;
  594. $matchInfo = $matchTable->info();
  595. $matchName = $matchInfo['name'];
  596. $matchSchema = isset($matchInfo['schema']) ? $matchInfo['schema'] : null;
  597. // automatically use the column named order from the inter-table if it exists
  598. if (null === $select && in_array('order', $intersectionTable->info('cols'))) {
  599. $select = $matchTable->select(false)->order($interName . '.order asc');
  600. }
  601. $db = $this->_getTable()->getAdapter();
  602. if (!$intersectionTable instanceof Zend_Db_Table_Abstract) {
  603. $type = gettype($intersectionTable);
  604. if ($type == 'object') {
  605. $type = get_class($intersectionTable);
  606. }
  607. throw new Zend_Db_Table_Row_Exception("Intersection table must be a Zend_Db_Table_Abstract, but it is $type");
  608. }
  609. // even if we are interacting between a table defined in a class and a
  610. // table via extension, ensure to persist the definition
  611. if (($tableDefinition = $this->_table->getDefinition()) !== null
  612. && ($intersectionTable->getDefinition() == null)
  613. ) {
  614. $intersectionTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  615. }
  616. if (is_string($matchTable)) {
  617. $matchTable = $this->_getTableFromString($matchTable);
  618. }
  619. if (!$matchTable instanceof Zend_Db_Table_Abstract) {
  620. $type = gettype($matchTable);
  621. if ($type == 'object') {
  622. $type = get_class($matchTable);
  623. }
  624. throw new Zend_Db_Table_Row_Exception("Match table must be a Zend_Db_Table_Abstract, but it is $type");
  625. }
  626. // even if we are interacting between a table defined in a class and a
  627. // table via extension, ensure to persist the definition
  628. if (($tableDefinition = $this->_table->getDefinition()) !== null
  629. && ($matchTable->getDefinition() == null)
  630. ) {
  631. $matchTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  632. }
  633. if ($select === null) {
  634. // debug - do not remove until fix
  635. //
  636. // Centurion_Db_Table_Abstract::setFiltersStatus(false);
  637. $select = $matchTable->select(false)->setIntegrityCheck(false);
  638. // debug - do not remove until fix
  639. //
  640. // Centurion_Db_Table_Abstract::restoreFiltersStatus();
  641. //
  642. // debug - do not remove until fix
  643. } else {
  644. $select->setTable($matchTable);
  645. }
  646. $matchMap = $this->_prepareReference($intersectionTable, $matchTable, $matchRefRule);
  647. for ($i = 0 ; $i < count($matchMap[Zend_Db_Table_Abstract::COLUMNS]) ; ++$i) {
  648. $interCol = $interDb->quoteIdentifier($interName . '.' . $matchMap[Zend_Db_Table_Abstract::COLUMNS][$i], true);
  649. $matchCol = $interDb->quoteIdentifier('m_' . $matchName . '.' . $matchMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i], true);
  650. $joinCond[] = "$interCol = $matchCol";
  651. }
  652. $joinCond = implode(' AND ', $joinCond);
  653. // debug - do not remove until fix
  654. //
  655. // $select->reset(Zend_Db_Select::FROM);
  656. // $select->reset(Zend_Db_Select::COLUMNS);
  657. // Centurion_Db_Table_Abstract::setFiltersStatus(false);
  658. // $select->from(array($interName), array(), $interSchema);
  659. //
  660. // echo "\n\n" . $select->__toString();
  661. //
  662. // $select->joinInner(array('m_' . $matchName => $matchName), $joinCond, Zend_Db_Select::SQL_WILDCARD, $matchSchema);
  663. // Centurion_Db_Table_Abstract::restoreFiltersStatus();
  664. //echo "\n" . $select->__toString();
  665. //
  666. // debug - do not remove until fix
  667. $select->from(array($interName), array(), $interSchema)
  668. ->joinInner(array('m_' . $matchName => $matchName), $joinCond, Zend_Db_Select::SQL_WILDCARD, $matchSchema)
  669. ->setIntegrityCheck(false);
  670. $callerMap = $this->_prepareReference($intersectionTable, $this->_getTable(), $callerRefRule);
  671. for ($i = 0 ; $i < count($callerMap[Zend_Db_Table_Abstract::COLUMNS]) ; ++$i) {
  672. $callerColumnName = $db->foldCase($callerMap[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  673. $value = $this->_data[$callerColumnName];
  674. $interColumnName = $interDb->foldCase($callerMap[Zend_Db_Table_Abstract::COLUMNS][$i]);
  675. $interCol = $interDb->quoteIdentifier("$interName.$interColumnName", true);
  676. $interInfo = $intersectionTable->info();
  677. $type = $interInfo[Zend_Db_Table_Abstract::METADATA][$interColumnName]['DATA_TYPE'];
  678. $select->where($interDb->quoteInto("$interCol = ?", $value, $type));
  679. }
  680. // debug - do not remove until fix
  681. //
  682. //echo "\n" . $select->__toString();
  683. //
  684. // debug - do not remove until fix
  685. $stmt = $select->query();
  686. $config = array(
  687. 'table' => $matchTable,
  688. 'data' => $stmt->fetchAll(Zend_Db::FETCH_ASSOC),
  689. 'rowClass' => $matchTable->getRowClass(),
  690. 'readOnly' => false,
  691. 'stored' => true
  692. );
  693. $rowsetClass = $matchTable->getRowsetClass();
  694. if (!class_exists($rowsetClass)) {
  695. try {
  696. Zend_Loader::loadClass($rowsetClass);
  697. } catch (Zend_Exception $e) {
  698. throw new Zend_Db_Table_Row_Exception($e->getMessage(), $e->getCode(), $e);
  699. }
  700. }
  701. $rowset = new $rowsetClass($config);
  702. return $rowset->setRefRow($this)
  703. ->setIntersectionTableClass($intersectionTable);
  704. }
  705. /**
  706. * Retrieve the previous row.
  707. *
  708. * @param string $by Column name
  709. * @param array $kwargs Arguments passed to the table
  710. * @param Centurion_Db_Table_Select $select The select used to process the query
  711. * @return Centurion_Db_Table_Row_Abstract
  712. */
  713. public function getFirstBy($by, $kwargs = null, $select = null)
  714. {
  715. return $this->_getFirstOrLastByField($by, true, $kwargs, $select);
  716. }
  717. /**
  718. * Retrieve the next row.
  719. *
  720. * @param string $by Column name
  721. * @param array $kwargs Arguments passed to the table
  722. * @param Centurion_Db_Table_Select $select The select used to process the query
  723. * @return Centurion_Db_Table_Row_Abstract
  724. */
  725. public function getLastBy($by, $kwargs = null, $select = null)
  726. {
  727. return $this->_getFirstOrLastByField($by, false, $kwargs, $select);
  728. }
  729. /**
  730. * Retrieve the previous row.
  731. *
  732. * @param string $by Column name
  733. * @param array $kwargs Arguments passed to the table
  734. * @param Centurion_Db_Table_Select $select The select used to process the query
  735. * @return Centurion_Db_Table_Row_Abstract
  736. */
  737. public function getPreviousBy($by, $kwargs = null, $select = null)
  738. {
  739. return $this->_getNextOrPreviousByField($by, false, $kwargs, $select);
  740. }
  741. /**
  742. * Retrieve the count of previous rows.
  743. *
  744. * @param string $by Column name
  745. * @param array $kwargs Arguments passed to the table
  746. * @param Centurion_Db_Table_Select $select The select used to process the query
  747. * @return Centurion_Db_Table_Row_Abstract
  748. */
  749. public function getPreviousCountBy($by, $kwargs = null, $select = null)
  750. {
  751. return $this->_getNextOrPreviousCountByField($by, false, $kwargs, $select);
  752. }
  753. /**
  754. * Retrieve the next row.
  755. *
  756. * @param string $by Column name
  757. * @param array $kwargs Arguments passed to the table
  758. * @param Centurion_Db_Table_Select $select The select used to process the query
  759. * @return Centurion_Db_Table_Row_Abstract
  760. */
  761. public function getNextBy($by, $kwargs = null, $select = null)
  762. {
  763. return $this->_getNextOrPreviousByField($by, true, $kwargs, $select);
  764. }
  765. /**
  766. * Retrieve the next row.
  767. *
  768. * @param string $by Column name
  769. * @param array $kwargs Arguments passed to the table
  770. * @param Centurion_Db_Table_Select $select The select used to process the query
  771. * @return Centurion_Db_Table_Row_Abstract
  772. */
  773. public function getPictureOrPxBy($by)
  774. {
  775. if (null !== $this->{$by}) {
  776. return $this->{$by};
  777. } else {
  778. return $this->getPx();
  779. }
  780. }
  781. /**
  782. * @return Media_Model_DbTable_Row_File
  783. */
  784. public function getPx()
  785. {
  786. return Centurion_Db::getSingleton('media/file')->getPx();
  787. }
  788. /**
  789. * Retrieve the count of next rows.
  790. *
  791. * @param string $by Column name
  792. * @param array $kwargs Arguments passed to the table
  793. * @param Centurion_Db_Table_Select $select The select used to process the query
  794. * @return Centurion_Db_Table_Row_Abstract
  795. */
  796. public function getNextCountBy($by, $kwargs = null, $select = null)
  797. {
  798. return $this->_getNextOrPreviousCountByField($by, true, $kwargs, $select);
  799. }
  800. /**
  801. * Generate a Zend_Date object with a column name.
  802. *
  803. * @param string $by Column name
  804. * @param string $dateFormat Zend_Date format
  805. * @return Zend_Date
  806. */
  807. public function getDateObjectBy($by, $dateFormat = Zend_Date::ISO_8601)
  808. {
  809. return new Zend_Date($this->{Centurion_Inflector::tableize($by)}, $dateFormat);
  810. }
  811. /**
  812. * @TODO: this function fail if multiple primary key
  813. * @return string
  814. */
  815. public function __toString()
  816. {
  817. return sprintf('%s-%s', get_class($this), $this->getPrimaryKey());
  818. }
  819. /**
  820. * Retrieve the absolute url of the instance.
  821. *
  822. * @return string
  823. */
  824. protected function _getAbsoluteUrl($urlParam = null)
  825. {
  826. //if (!isset($this->_data['permalink'])) {
  827. if (null === $urlParam) {
  828. $result = $this->getAbsoluteUrl();
  829. } else {
  830. $result = $urlParam;
  831. }
  832. if (null === $result) {
  833. return '#';
  834. }
  835. list($params, $route) = $result;
  836. if (is_string($params)) {
  837. $temp = $route;
  838. $route = $params;
  839. $params = $temp;
  840. }
  841. $this->_data['permalink'] = Zend_Controller_Front::getInstance()->getRouter()->assemble($params, $route, true);
  842. //}
  843. return $this->_data['permalink'];
  844. }
  845. protected function _getAdminUrl()
  846. {
  847. $route = 'default';
  848. $params = array();
  849. $tab = explode('_', $this->getTable()->info(Zend_Db_Table::NAME));
  850. $params['module'] = array_shift($tab);
  851. $params['controller'] = 'admin-' . implode('-', $tab);
  852. $params['id'] = $this->id;
  853. $params['action'] = 'get';
  854. $this->_data['admin_permalink'] = Zend_Controller_Front::getInstance()->getRouter()->assemble($params, $route, true);
  855. return $this->_data['admin_permalink'];
  856. }
  857. /**
  858. * Retrieve the absolute url of the instance.
  859. *
  860. * @return string
  861. */
  862. public function getAbsoluteUrl()
  863. {
  864. $router = Zend_Controller_Front::getInstance()->getRouter();
  865. $name = sprintf('%s_%s', $this->getTable()->info(Centurion_Db_Table_Abstract::NAME), 'get');
  866. if (!$router->hasRoute($name)) {
  867. $data = explode('_', $this->getTable()->info(Centurion_Db_Table_Abstract::NAME), 2);
  868. return array(
  869. array(
  870. 'module' => $data[0],
  871. 'controller' => str_replace('_', '-', $data[1]),
  872. 'action' => 'get',
  873. 'id' => $this->id
  874. ), 'default'
  875. );
  876. }
  877. $route = $router->getRoute($name);
  878. if (!($route instanceof Centurion_Controller_Router_Route_Object || $route instanceof Zend_Controller_Router_Route_Chain)) {
  879. throw new Centurion_Exception(sprintf('%s route is not a Centurion_Controller_Router_Route_Object. Please overload getAbsoluteUrl() function. ', $name));
  880. }
  881. return array(array('object' => $this), $name);
  882. }
  883. /**
  884. * Retrieve the primary key value.
  885. *
  886. * @return array|string|int
  887. */
  888. public function getPrimaryKey()
  889. {
  890. $primary = $this->_getPrimaryKey(true);
  891. if (count($primary) === 1) {
  892. return current($primary);
  893. }
  894. return $primary;
  895. }
  896. public function getCacheTag($relation = null)
  897. {
  898. if (null === $relation) {
  899. $pk = $this->pk;
  900. if (is_string($pk) || is_int($pk)) {
  901. $pk = md5(serialize($pk));
  902. }
  903. return sprintf('__%s__%s', $this->getTable()->info(Centurion_Db_Table_Abstract::NAME), $pk);
  904. } else {
  905. $dependentTables = $this->getTable()->info('dependentTables');
  906. if (isset($dependentTables[$relation])) {
  907. $pk = is_string($this->pk) || is_int($this->pk) ? $this->pk : md5(serialize($this->pk));
  908. return sprintf('__%s__%s__%s', $dependentTables[$relation], get_class($this), $pk);
  909. }
  910. }
  911. }
  912. public function getReverseCacheTagForRelated($key)
  913. {
  914. $row = $this->{$key};
  915. if (null === $row) {
  916. return null;
  917. }
  918. $pk = is_string($row->pk) || is_int($row->pk) ? $row->pk : md5(serialize($row->pk));
  919. return sprintf('__%s__%s__%s', get_class($this->getTable()), get_class($row), $pk);
  920. }
  921. /**
  922. * Has selector, with a type, known if an object is a parent of another.
  923. * ex: if an user has a post.
  924. *
  925. * @param string $type
  926. * @param string $pk
  927. * @todo add manyDependentTables
  928. * @TODO : this is time and memory consume. It make fetchAll only to test if exist
  929. * @return void
  930. * @todo: this is mememory consuming, time consuming, and mysql consuming. We could check with pk
  931. */
  932. public function has($type, $object)
  933. {
  934. if (!array_key_exists($type, $this->getTable()->getDependentTables())
  935. && !array_key_exists($type, $this->getTable()->getManyDependentTables())
  936. ) {
  937. throw new Centurion_Db_Exception(sprintf('type "%s" does not belong to %s', $type, get_class($this->getTable())));
  938. }
  939. if ($object instanceof Centurion_Db_Table_Row_Abstract) {
  940. $object = $object->toArray();
  941. }
  942. return in_array($object, $this->{$type}->toArray());
  943. }
  944. /**
  945. * @throws Zend_Db_Table_Row_Exception
  946. * @param $columnName
  947. * @param $order
  948. * @return Zend_Db_Table_Rowset_Abstract
  949. * @todo documentation
  950. */
  951. public function findDependentRowsetOrdered($columnName, $order)
  952. {
  953. $dependentTables = $this->getTable()->info('dependentTables');
  954. if (isset($dependentTables[$columnName])) {
  955. $select = $this->_getTableFromString($dependentTables[$columnName])->select()->order($order);
  956. $rowset = $this->findDependentRowset($dependentTables[$columnName], null, $select);
  957. if (!isset($this->_children[$columnName])) {
  958. $this->_children[$columnName] = $rowset;
  959. return $rowset;
  960. }
  961. return $this->_children[$columnName];
  962. }
  963. throw new Zend_Db_Table_Row_Exception(sprintf('%s is not in dependent table', $columnName));
  964. }
  965. /**
  966. * Query a dependent table to retrieve rows matching the current row.
  967. *
  968. * @param string|Zend_Db_Table_Abstract $dependentTable
  969. * @param string OPTIONAL $ruleKey
  970. * @param Zend_Db_Table_Select OPTIONAL $select
  971. * @return Zend_Db_Table_Rowset_Abstract Query result from $dependentTable
  972. * @throws Zend_Db_Table_Row_Exception If $dependentTable is not a table or is not loadable.
  973. */
  974. public function findDependentRowset($dependentTable, $ruleKey = null, Zend_Db_Table_Select $select = null)
  975. {
  976. $db = $this->_getTable()->getAdapter();
  977. if (is_string($dependentTable)) {
  978. $dependentTable = $this->_getTableFromString($dependentTable);
  979. }
  980. if (!$dependentTable instanceof Zend_Db_Table_Abstract) {
  981. $type = gettype($dependentTable);
  982. if ($type == 'object') {
  983. $type = get_class($dependentTable);
  984. }
  985. throw new Zend_Db_Table_Row_Exception("Dependent table must be a Zend_Db_Table_Abstract, but it is $type");
  986. }
  987. // even if we are interacting between a table defined in a class and a
  988. // table via extension, ensure to persist the definition
  989. if (($tableDefinition = $this->_table->getDefinition()) !== null
  990. && ($dependentTable->getDefinition() == null)
  991. ) {
  992. $dependentTable->setOptions(array(Zend_Db_Table_Abstract::DEFINITION => $tableDefinition));
  993. }
  994. if ($select === null) {
  995. $select = $dependentTable->select();
  996. } else {
  997. $select->setTable($dependentTable);
  998. }
  999. $map = $this->_prepareReference($dependentTable, $this->_getTable(), $ruleKey);
  1000. for ($i = 0 ; $i < count($map[Zend_Db_Table_Abstract::COLUMNS]) ; ++$i) {
  1001. $parentColumnName = $db->foldCase($map[Zend_Db_Table_Abstract::REF_COLUMNS][$i]);
  1002. $value = $this->_data[$parentColumnName];
  1003. // Use adapter from dependent table to ensure correct query construction
  1004. $dependentDb = $dependentTable->getAdapter();
  1005. $dependentColumnName = $dependentDb->foldCase($map[Zend_Db_Table_Abstract::COLUMNS][$i]);
  1006. $dependentColumn = $dependentDb->quoteIdentifier($dependentColumnName, true);
  1007. $dependentInfo = $dependentTable->info();
  1008. $type = $dependentInfo[Zend_Db_Table_Abstract::METADATA][$dependentColumnName]['DATA_TYPE'];
  1009. $select->where($dependentInfo['name'] . '.' . "$dependentColumn = ?", $value, $type);
  1010. }
  1011. return $dependentTable->fetchAll($select);
  1012. }
  1013. public function findDependentRowsetByColumnName($column, $wheres = null)
  1014. {
  1015. $dependentTables = $this->getTable()->info('dependentTables');
  1016. if (isset($dependentTables[$column])) {
  1017. $dependentTable = $dependentTables[$column];
  1018. if (is_string($dependentTable)) {
  1019. $dependentTable = $this->_getTableFromString($dependentTable);
  1020. }
  1021. if (null === $wheres || !($wheres instanceof Zend_Db_Table_Select)) {
  1022. $select = $dependentTable->select();
  1023. if (is_array($wheres) || is_string($wheres) || $wheres instanceof Zend_Db_Expr) {
  1024. foreach ((array) $wheres as $key => $where) {
  1025. $select->where($key, $where);
  1026. }
  1027. }
  1028. } else {
  1029. $select = $wheres->setTable($dependentTable);
  1030. }
  1031. return $this->findDependentRowset($dependentTable, null, $select)->setRefRow($this);
  1032. }
  1033. throw new Zend_Db_Table_Select_Exception(sprintf('Column %s is not in dependent list', $column));
  1034. }
  1035. /**
  1036. * @return mixed
  1037. * @todo documentation
  1038. */
  1039. public function save()
  1040. {
  1041. $this->_preSave();
  1042. $result = parent::save();
  1043. $this->_postSave();
  1044. return $result;
  1045. }
  1046. /**
  1047. * @return void
  1048. * @todo documentation
  1049. */
  1050. protected function _notifyCacheForRelated()
  1051. {
  1052. foreach ($this->getTable()->info('referenceMap') as $key => $val) {
  1053. $tags = $this->getReverseCacheTagForRelated($key);
  1054. if ($tags !== null) {
  1055. Centurion_Signal::factory('clean_cache')->send($this, array(
  1056. Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG,
  1057. array($tags)
  1058. ));
  1059. }
  1060. }
  1061. }
  1062. /**
  1063. * override these methods to implement pre/post save logic
  1064. */
  1065. protected function _preSave()
  1066. {
  1067. Centurion_Signal::factory('pre_save')->send($this);
  1068. $this->_notifyCacheForRelated();
  1069. }
  1070. protected function _postSave()
  1071. {
  1072. Centurion_Signal::factory('post_save')->send($this);
  1073. $this->_notifyCacheForRelated();
  1074. }
  1075. /**
  1076. * Allows pre-insert logic to be applied to row.
  1077. * Subclasses may override this method.
  1078. *
  1079. * @return void
  1080. */
  1081. protected function _insert()
  1082. {
  1083. Centurion_Signal::factory('pre_insert')->send($this);
  1084. }
  1085. /**
  1086. * Allows post-insert logic to be applied to row.
  1087. * Subclasses may override this method.
  1088. *
  1089. * @return void
  1090. */
  1091. protected function _postInsert()
  1092. {
  1093. Centurion_Signal::factory('post_insert')->send($this);
  1094. $this->_notifyCacheForRelated();
  1095. }
  1096. /**
  1097. * Allows pre-update logic to be applied to row.
  1098. * Subclasses may override this method.
  1099. *
  1100. * @return void
  1101. */
  1102. protected function _update()
  1103. {
  1104. Centurion_Signal::factory('pre_update')->send($this);
  1105. $this->_notifyCacheForRelated();
  1106. }
  1107. /**
  1108. * Allows post-update logic to be applied to row.
  1109. * Subclasses may override this method.
  1110. *
  1111. * @return void
  1112. */
  1113. protected function _postUpdate()
  1114. {
  1115. Centurion_Signal::factory('post_update')->send($this);
  1116. $this->_notifyCacheForRelated();
  1117. }
  1118. /**
  1119. * Allows pre-delete logic to be applied to row.
  1120. * Subclasses may override this method.
  1121. *
  1122. * @return void
  1123. */
  1124. protected function _delete()
  1125. {
  1126. Centurion_Signal::factory('pre_delete')->send($this);
  1127. $this->_notifyCacheForRelated();
  1128. }
  1129. /**
  1130. * Allows post-delete logic to be applied to row.
  1131. * Subclasses may override this method.
  1132. *
  1133. * @return void
  1134. */
  1135. protected function _postDelete()
  1136. {
  1137. Centurion_Signal::factory('post_delete')->send($this);
  1138. }
  1139. /**
  1140. * Override the getter, if we get and related object of a related object.
  1141. *
  1142. * @param string $tableName
  1143. * @return void
  1144. */
  1145. protected function _getTableFromString($tableName)
  1146. {
  1147. return Centurion_Db::getSingletonByClassName($tableName);
  1148. }
  1149. /**
  1150. * Retrieve the next or previous row.
  1151. *
  1152. * @param string $by Column name
  1153. * @param boolean $isNext Next row if true, previous instead
  1154. * @param array $kwargs Arguments passed to the table
  1155. * @param Centurion_Db_Table_Select $select The select used to process the query
  1156. * @return Centurion_Db_Table_Row_Abstract
  1157. */
  1158. protected function _getFirstOrLastByField($by, $isFirst = true, $kwargs = null, $select = null)
  1159. {
  1160. if (is_string($by)) {
  1161. $column = Centurion_Inflector::tableize($by);
  1162. }
  1163. if (null === $select) {
  1164. $select = $this->getTable()->select(true);
  1165. }
  1166. return $this->_getFirstOrLastSelectByField($column, $isFirst, $k

Large files files are truncated, but you can click here to view the full file