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

/library/Bvb/Grid/Source/Doctrine.php

https://bitbucket.org/mayorbrain/precurio-v2
PHP | 1090 lines | 543 code | 147 blank | 400 comment | 65 complexity | 4d602ffbba825766b1ddeb725356ee49 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-2.1, BSD-3-Clause, LGPL-2.0, CC-BY-SA-3.0, MIT
  1. <?php
  2. class Bvb_Grid_Source_Doctrine implements Bvb_Grid_Source_Interface
  3. {
  4. /**
  5. * Stores the supplied Doctrine_Query
  6. *
  7. * @var Doctrine_Query
  8. */
  9. protected $_query;
  10. /**
  11. * Stores the parsed out DQL
  12. *
  13. * @var array
  14. */
  15. protected $_queryParts = array(
  16. 'select' => array(),
  17. 'from' => array(),
  18. 'join' => array()
  19. );
  20. /**
  21. * Intialize the Doctrine_Query. We will parse out
  22. * all the provided DQL or start a Doctrine_Query
  23. * if an instance of Doctrine_Record is provided
  24. *
  25. * @param mixed $q
  26. */
  27. public function __construct($q)
  28. {
  29. if ($q instanceof Doctrine_Record) {
  30. $q = get_class($q);
  31. }
  32. if (is_string($q)) {
  33. $q = Doctrine_Query::create()->from($q);
  34. }
  35. if (!$q instanceof Doctrine_Query) {
  36. require_once 'Bvb/Grid/Source/Doctrine/Exception.php';
  37. throw new Bvb_Grid_Source_Doctrine_Exception(
  38. "Please provide only an instance of Doctrine_Query "
  39. . "or a valid Doctrine_Record instance"
  40. );
  41. }
  42. $this->_query = $q;
  43. $this->_setFromParts();
  44. $this->_setSelectParts();
  45. }
  46. /**
  47. * Simple method for the Grid to determine if this
  48. * Source can handle CRUD
  49. *
  50. * @return boolean
  51. */
  52. public function hasCrud()
  53. {
  54. return true;
  55. }
  56. /**
  57. * Returns the "main" table
  58. * the one after select * FROM {MAIN_TABLE}
  59. *
  60. * @return array
  61. */
  62. public function getMainTable()
  63. {
  64. $table = Doctrine::getTable($this->_queryParts['from']['tableModel']);
  65. return array('table' => $table->getTableName());
  66. }
  67. /**
  68. * builds a key=>value array
  69. *
  70. * they must have two options
  71. * title and field
  72. * field is used to perform queries.
  73. * Must have table name or table alias as a prefix
  74. * ex: user.id | country.population
  75. *
  76. * The key for this array is the output field
  77. * If raw sql is somehting like
  78. *
  79. * select name as alias, country from users
  80. *
  81. * the return array must be like this:
  82. *
  83. * array('alias'=>array('title'=>'alias','field'=>'users.name'));
  84. *
  85. * its not bad idea to apply this to fields titles
  86. * $title = ucwords(str_replace('_',' ',$title));
  87. *
  88. * @return array
  89. */
  90. public function buildFields()
  91. {
  92. $this->_setSelectParts();
  93. return $this->_queryParts['select'];
  94. }
  95. /**
  96. * Use the supplied Doctrine_Query to find its primary ID
  97. *
  98. * TODO : Implement usage of $table param
  99. */
  100. public function getPrimaryKey($table = null)
  101. {
  102. $return = array();
  103. $table = Doctrine::getTable($this->_queryParts['from']['tableModel']);
  104. $alias = $this->_queryParts['from']['alias'];
  105. //Get the Primary Key(s) for provided table
  106. $ids = $table->getIdentifierColumnNames();
  107. //Append the alias to each Primary key field
  108. foreach ($ids as $id) {
  109. $return[] = (!empty($alias)) ? $alias . '.' . $id : $id;
  110. }
  111. return $return;
  112. }
  113. /**
  114. * Gets a unique record as a associative array
  115. *
  116. * @param $table
  117. * @param $condition
  118. * @return array
  119. */
  120. public function getRecord($table, array $condition)
  121. {
  122. $tableModel = $this->_getModelFromTable($table);
  123. $query = Doctrine_Query::create()->from($tableModel);
  124. foreach ($condition as $field => $value) {
  125. $query->addWhere($field . ' = ?', $value);
  126. }
  127. $results = $query->fetchArray(array(), Doctrine::HYDRATE_SCALAR);
  128. $newResults = $this->_cleanQueryResults($results);
  129. if (count($newResults) == 1) {
  130. return $newResults[0];
  131. }
  132. return $newResults;
  133. }
  134. /**
  135. * Should return the database server name or source name
  136. *
  137. * Ex: mysql, pgsql, array, xml
  138. *
  139. * @return string
  140. */
  141. public function getSourceName()
  142. {
  143. return strtolower($this->_query->getConnection()->getDriverName());
  144. }
  145. /**
  146. * Runs the query and returns the result as a associative array
  147. *
  148. * @return array
  149. */
  150. public function execute()
  151. {
  152. $newQuery = clone $this->_query;
  153. $results = $newQuery->execute(array(), Doctrine::HYDRATE_SCALAR);
  154. $newArray = $this->_cleanQueryResults($results);
  155. return $newArray;
  156. }
  157. /**
  158. * Get a record detail based the current query
  159. *
  160. * <code>
  161. * $where = array(
  162. * array('columnName' => 'searchValue')
  163. * )
  164. * </code>
  165. *
  166. * @param array $where
  167. * @return array
  168. */
  169. public function fetchDetail(array $where)
  170. {
  171. /**
  172. * Remove these since we are trying to retrieve
  173. * a specific row
  174. */
  175. $this->_query->removeDqlQueryPart('limit')
  176. ->removeDqlQueryPart('offset');
  177. foreach ($where as $column => $value) {
  178. $this->_query->addWhere($column . ' = ?', $value);
  179. }
  180. return $this->execute();
  181. }
  182. /**
  183. * Return the total of records
  184. *
  185. * @return integer
  186. */
  187. public function getTotalRecords()
  188. {
  189. return (int) $this->_query->count();
  190. }
  191. /**
  192. * Ex: array('c'=>array('tableName'=>'Country'));
  193. * where c is the table alias. If the table as no alias,
  194. * c should be the table name
  195. *
  196. * @return array
  197. */
  198. function getTableList ()
  199. {
  200. die('getTableList');
  201. }
  202. /**
  203. * Return possible filters values based on field definion
  204. * This is mostly used for enum fields where the possibile
  205. * values are extracted
  206. *
  207. * Ex: enum('Yes','No','Empty');
  208. *
  209. * should return
  210. *
  211. * array('Yes'=>'Yes','No'=>'No','Empty'=>'Empty');
  212. *
  213. * @param $field
  214. * @return string
  215. */
  216. public function getFilterValuesBasedOnFieldDefinition($field)
  217. {
  218. $table = $this->_getModelFromColumn($field);
  219. $tableClass = Doctrine::getTable($table);
  220. if (strpos($field, '.') !== false) {
  221. list($alias, $column) = explode('.', $field);
  222. } else {
  223. $column = $field;
  224. }
  225. $definition = $tableClass->getDefinitionOf($column);
  226. if ($definition['type'] == 'enum') {
  227. foreach ($definition['values'] as $val) {
  228. $return[$val] = $val;
  229. }
  230. return $return;
  231. }
  232. return 'text';
  233. }
  234. /**
  235. * Return te field type
  236. * char, varchar, int
  237. *
  238. * Note: If the field is enum or set,
  239. * the value returned must be set or enum,
  240. * and not the full definition
  241. *
  242. * @param string $field
  243. * @return string|null
  244. */
  245. public function getFieldType($field)
  246. {
  247. $tableModel = $this->_getModelFromColumn($field);
  248. if (isset($this->_queryParts['select'][$field]['field'])) {
  249. if (strpos($this->_queryParts['select'][$field]['field'], '.') !== false) {
  250. list($alias, $fieldName) = explode('.', $this->_queryParts['select'][$field]['field']);
  251. } else {
  252. $fieldName = $field;
  253. }
  254. } else {
  255. return null;
  256. }
  257. $fieldType = Doctrine::getTable($tableModel)->getTypeOfColumn($fieldName);
  258. return $fieldType;
  259. }
  260. /**
  261. *
  262. * Build the order part from the query.
  263. *
  264. * The first arg is the field to be ordered and the $order
  265. * arg is the correspondent order (ASC|DESC)
  266. *
  267. * If the $reset is set to true, all previous order should be removed
  268. *
  269. * @param string $field
  270. * @param string $order
  271. * @param bool $reset
  272. * @return Bvb_Grid_Source_Doctrine
  273. */
  274. public function buildQueryOrder($field, $order, $reset = false)
  275. {
  276. if ($reset) {
  277. $this->_query->removeDqlQueryPart('orderby');
  278. }
  279. $this->_query->addOrderBy($field . ' ' . $order);
  280. return $this;
  281. }
  282. /**
  283. * Build the query limit clause
  284. *
  285. * @param $start
  286. * @param $offset
  287. * @return Bvb_Grid_Source_Doctrine
  288. */
  289. public function buildQueryLimit($start, $offset)
  290. {
  291. $this->_query->limit($start)->offset($offset);
  292. return $this;
  293. }
  294. /**
  295. * Returns the select object
  296. */
  297. function getSelectObject ()
  298. {
  299. die('getSelectObject');
  300. }
  301. /**
  302. * Returns the selected order
  303. * that was defined by the user in the query entered
  304. * and not the one generated by the system
  305. *
  306. * If empty an empty array must be returned.
  307. *
  308. * Else the array must be like this:
  309. *
  310. * <code>
  311. * $return = array(
  312. * 0 => field
  313. * 1 => (ASC|DESC)
  314. * );
  315. * </code>
  316. *
  317. * @return array
  318. */
  319. public function getSelectOrder()
  320. {
  321. $newOrderBys = array();
  322. $orderBy = $this->_query->getDqlPart('orderby');
  323. if (!empty($orderBy)) {
  324. foreach ($orderBy as $anOrderby) {
  325. $orderBys = explode(',', $anOrderby);
  326. foreach ($orderBys as $order) {
  327. $parts = explode(' ', trim($order));
  328. if (strtolower($parts[1]) != 'desc' && strtolower($parts[1]) != 'asc') {
  329. $parts[1] = '';
  330. }
  331. $newOrderBys[] = $parts;
  332. }
  333. }
  334. }
  335. return $newOrderBys;
  336. }
  337. /**
  338. * Should preform a query based on the provided by the user
  339. * select the two fields and return an array $field=>$value
  340. * as result
  341. *
  342. * ex: SELECT $field, $value FROM *
  343. * array('1'=>'Something','2'=>'Number','3'=>'history')....;
  344. *
  345. * @param string $field
  346. * @param string $value
  347. * @return array
  348. */
  349. public function getDistinctValuesForFilters($field, $value)
  350. {
  351. $return = array();
  352. $newQuery = clone $this->_query;
  353. $distinct = new Doctrine_Expression("DISTINCT($field)");
  354. $newQuery->removeDqlQueryPart('select')
  355. ->removeDqlQueryPart('orderby')
  356. ->removeDqlQueryPart('limit')
  357. ->removeDqlQueryPart('offset')
  358. ->select("$distinct AS field, $value AS value")
  359. ->orderBy('value ASC');
  360. $results = $newQuery->fetchArray();
  361. foreach ($results as $value ) {
  362. $return[$value['field']] = $value['value'];
  363. }
  364. return $return;
  365. }
  366. /**
  367. * Perform a sqlexp
  368. *
  369. * $value = array ('functions' => array ('AVG'), 'value' => 'Population' );
  370. *
  371. * Should be converted to
  372. * SELECT AVG(Population) FROM *
  373. *
  374. * $value = array ('functions' => array ('SUM','AVG'), 'value' => 'Population' );
  375. *
  376. * Should be converted to
  377. * SELECT SUM(AVG(Population)) FROM *
  378. *
  379. * @param array $value
  380. */
  381. public function getSqlExp(array $value)
  382. {
  383. $return = array();
  384. $newQuery = clone $this->_query;
  385. foreach (array_reverse($value['functions']) as $key => $func) {
  386. if ($key == 0) {
  387. $exp = $func . '(' . $value['value'] . ')';
  388. } else {
  389. $exp = $func . '(' . $exp . ')';
  390. }
  391. }
  392. $exp = new Doctrine_Expression($exp);
  393. $newQuery->removeDqlQueryPart('select')
  394. ->removeDqlQueryPart('orderby')
  395. ->removeDqlQueryPart('limit')
  396. ->removeDqlQueryPart('offset')
  397. ->select("$exp AS total");
  398. $result = $newQuery->fetchOne(array(), Doctrine::HYDRATE_ARRAY);
  399. return $result['total'];
  400. }
  401. /**
  402. * Adds a fulltext search instead of a addcondition method
  403. *
  404. * $field has an index search
  405. * $field['search'] = array('extra'=>'boolean|queryExpansion','indexes'=>'string|array');
  406. *
  407. * if no indexes provided, use the field name
  408. *
  409. * boolean => IN BOOLEAN MODE
  410. * queryExpansion => WITH QUERY EXPANSION
  411. *
  412. * @param $filter
  413. * @param $field
  414. */
  415. function addFullTextSearch ($filter, $field)
  416. {
  417. die('addFullTextSearch');
  418. }
  419. /**
  420. * Adds a new condition to the current query
  421. * $filter is the value to be filtered
  422. * $op is the opreand to be used: =,>=, like, llike,REGEX,
  423. * $completeField. use the index $completField['field'] to
  424. * specify the field, to avoid ambiguous
  425. *
  426. * @param $filter
  427. * @param $op
  428. * @param $completeField
  429. * @return Bvb_Grid_Source_Doctrine
  430. */
  431. public function addCondition($filter, $op, $completeField)
  432. {
  433. $field = $completeField['field'];
  434. switch (strtolower($op)) {
  435. case 'equal':
  436. case '=':
  437. $this->_query->addWhere($field . ' = ?', $filter);
  438. break;
  439. case 'regex':
  440. $this->_query->addWhere($field . " REGEXP ?", $filter);
  441. break;
  442. case 'rlike':
  443. $this->_query->addWhere($field . " LIKE ?", $filter . "%");
  444. break;
  445. case 'llike':
  446. $this->_query->addWhere($field . " LIKE ?", "%" . $filter);
  447. break;
  448. case '>=':
  449. $this->_query->addWhere($field . " >= ?", $filter);
  450. break;
  451. case '>':
  452. $this->_query->addWhere($field . " > ?", $filter);
  453. break;
  454. case '<>':
  455. case '!=':
  456. $this->_query->addWhere($field . " <> ?", $filter);
  457. break;
  458. case '<=':
  459. $this->_query->addWhere($field . " <= ?", $filter);
  460. break;
  461. case '<':
  462. $this->_query->addWhere($field . " < ?", $filter);
  463. break;
  464. case 'in':
  465. $filter = explode(',', $filter);
  466. $this->_query->whereIn($field, $filter);
  467. break;
  468. case 'range':
  469. $start = substr($filter, 0, strpos($filter, '<>'));
  470. $end = substr($filter, strpos($filter, '<>') + 2);
  471. $this->_query->addWhere($field . " between ? and ?", array($start, $end));
  472. break;
  473. case 'like':
  474. default:
  475. $this->_query->addWhere($field . " LIKE ?", "%" . $filter . "%");
  476. break;
  477. }
  478. return $this;
  479. }
  480. /**
  481. * Insert an array of key=>values in the specified table
  482. *
  483. * @param string $table
  484. * @param array $post
  485. * @return boolean
  486. */
  487. public function insert($table, array $post)
  488. {
  489. $tableModel = $this->_getModelFromTable($table);
  490. $id = Doctrine::getTable($tableModel)->getIdentifier();
  491. /**
  492. * @var Doctrine_Record
  493. */
  494. $model = new $tableModel;
  495. $model->fromArray($post);
  496. return $model->trySave();
  497. }
  498. /**
  499. * Update values in a table using the $condition clause
  500. *
  501. * The condition clause is a $field=>$value array
  502. * that should be escaped by YOU (if your class doesn't do that for you)
  503. * and usinf the AND operand
  504. *
  505. * Ex: array('user_id'=>'1','id_site'=>'12');
  506. *
  507. * Raw SQL: * WHERE user_id='1' AND id_site='12'
  508. *
  509. * @param string $table
  510. * @param array $post
  511. * @param array $condition
  512. * @return integer Of Affected rows
  513. */
  514. public function update($table, array $post, array $condition)
  515. {
  516. $tableModel = $this->_getModelFromTable($table);
  517. $query = Doctrine_Query::create()->update($tableModel);
  518. foreach ($post as $field => $value) {
  519. $query->set($field, '?', $value);
  520. }
  521. foreach ($condition as $field => $value) {
  522. $query->addWhere($field . ' = ?', $value);
  523. }
  524. return $query->execute();
  525. }
  526. /**
  527. * Delete a record from a table
  528. *
  529. * The condition clause is a $field=>$value array
  530. * that should be escaped by YOU (if your class doesn't do that for you)
  531. * and usinf the AND operand
  532. *
  533. * Ex: array('user_id'=>'1','id_site'=>'12');
  534. * Raw SQL: * WHERE user_id='1' AND id_site='12'
  535. *
  536. * @param string $table
  537. * @param array $condition
  538. * @return integer Of Affected rows
  539. */
  540. public function delete($table, array $condition)
  541. {
  542. $tableModel = $this->_getModelFromTable($table);
  543. $query = Doctrine_Query::create()->delete($tableModel);
  544. foreach ($condition as $field => $value) {
  545. $query->addWhere($field . ' = ?', $value);
  546. }
  547. return $query->execute();
  548. }
  549. /**
  550. * Removes any order in query
  551. *
  552. * @return Bvb_Grid_Source_Doctrine
  553. */
  554. public function resetOrder()
  555. {
  556. $this->_query->removeDqlQueryPart('orderby');
  557. return $this;
  558. }
  559. /**
  560. * Cache handler.
  561. *
  562. * TODO: Research what 'cache' does, might just need to look at the
  563. * bool and see if we need to set Doctrine Cache or not
  564. */
  565. function setCache($cache)
  566. {
  567. //die(Zend_Debug::dump($cache));
  568. //die('setCache');
  569. }
  570. /**
  571. * Build the form based on a Model or query
  572. *
  573. * @return array
  574. */
  575. public function buildForm()
  576. {
  577. $table = $this->_queryParts['from']['tableModel'];
  578. $columns = Doctrine::getTable($table)->getColumns();
  579. return $this->buildFormElements($columns);
  580. }
  581. /**
  582. * Will build out an array of form elements,
  583. * based on the column type and return the array
  584. * to be used when loading the Bvb_Grid_Form
  585. *
  586. * @param array $cols
  587. * @param array $info
  588. * @return array
  589. */
  590. public function buildFormElements(array $cols, $info = array())
  591. {
  592. $form = array();
  593. foreach ($cols as $column => $detail) {
  594. if (isset($detail['primary']) && $detail['primary']) {
  595. continue;
  596. }
  597. $label = ucwords(str_replace('_', ' ', $column));
  598. switch ($detail['type']) {
  599. case 'enum':
  600. $form['elements'][$column] = array('select', array('multiOptions' => $detail['values'], 'required' => ($detail['notnull'] == 1) ? false : true, 'label' => $label));
  601. break;
  602. case 'string':
  603. case 'varchar':
  604. case 'char':
  605. $length = $detail['length'];
  606. $form['elements'][$column] = array('text', array('validators' => array(array('stringLength', false, array(0, $length))), 'size' => 40, 'label' => $label, 'required' => ($detail['notnull'] == 1) ? false : true, 'value' => (! empty($detail['default']) ? $detail['default'] : "")));
  607. break;
  608. case 'date':
  609. $form['elements'][$column] = array('text', array('validators' => array(array('Date')), 'size' => 10, 'label' => $label, 'required' => ($detail['notnull'] == 1) ? false : true, 'value' => (! empty($detail['default']) ? $detail['default'] : "")));
  610. break;
  611. case 'datetime':
  612. case 'timestamp':
  613. $form['elements'][$column] = array('text', array('validators' => array(array(new Zend_Validate_Date('Y-m-d H:i:s'))), 'size' => 19, 'label' => $label, 'required' => ($detail['notnull'] == 1) ? false : true, 'value' => (! empty($detail['default']) ? $detail['default'] : "")));
  614. break;
  615. case 'text':
  616. case 'mediumtext':
  617. case 'longtext':
  618. case 'smalltext':
  619. $form['elements'][$column] = array('textarea', array('label' => $label, 'required' => ($detail['notnull'] == 1) ? false : true, 'filters' => array('StripTags')));
  620. break;
  621. case 'integer':
  622. case 'int':
  623. case 'bigint':
  624. case 'mediumint':
  625. case 'smallint':
  626. case 'tinyint':
  627. $isZero = (! empty($detail['default']) && $detail['default'] == "0") ? true : false;
  628. $form['elements'][$column] = array('text', array('validators' => array('Digits'), 'label' => $label, 'size' => 10, 'required' => ($isZero == false && $detail['notnull'] == 1) ? false : true, 'value' => (! empty($detail['default']) ? $detail['default'] : "")));
  629. break;
  630. case 'float':
  631. case 'decimal':
  632. case 'double':
  633. $form['elements'][$column] = array('text', array('validators' => array('Float'), 'size' => 10, 'label' => $label, 'required' => ($detail['notnull'] == 1) ? false : true, 'value' => (! empty($detail['default']) ? $detail['default'] : "")));
  634. break;
  635. default:
  636. break;
  637. }
  638. }
  639. return $form;
  640. }
  641. /**
  642. * Used within this class to clean the hydrated result
  643. * to be something more Grid friendly
  644. *
  645. * @param array $results
  646. * @return array Cleaned results
  647. */
  648. protected function _cleanQueryResults($results)
  649. {
  650. $newArray = array();
  651. if (empty($this->_queryParts['from']['alias'])) {
  652. foreach ($results as $rows) {
  653. $temp = array();
  654. foreach ($rows as $col => $val) {
  655. $name = str_replace($this->_queryParts['from']['tableModel'] . '_', '', $col);
  656. $temp[$name] = $val;
  657. }
  658. $newArray[] = $temp;
  659. }
  660. } else {
  661. foreach ($results as $rows) {
  662. $temp = array();
  663. foreach ($rows as $col => $val) {
  664. $parts = explode('_', $col, 2);
  665. foreach ($this->_queryParts['select'] as $alias => $select) {
  666. if (implode('.', $parts) == $select['field'] || $parts[1] == $alias) {
  667. $temp[$alias] = $val;
  668. }
  669. }
  670. }
  671. $newArray[] = $temp;
  672. }
  673. }
  674. return $newArray;
  675. }
  676. /**
  677. * Used to parse out the SELECT pieces of the DQL
  678. * and place it in the $_queryParts array for use
  679. * in many other places
  680. *
  681. * @return Bvb_Grid_Source_Doctrine
  682. */
  683. protected function _setSelectParts()
  684. {
  685. $return = array();
  686. $selects = $this->_query->getDqlPart('select');
  687. if (empty($selects)) {
  688. $this->_findAndSetSelect();
  689. $selects = $this->_query->getDqlPart('select');
  690. }
  691. //Remove all 'as' instances
  692. $selects = $this->_removeAs($selects);
  693. foreach ($selects as $select) {
  694. $fields = explode(',', $select);
  695. $fields = array_map('trim', $fields);
  696. foreach ($fields as $field) {
  697. $fieldName = trim($field);
  698. $fieldAlias = null;
  699. if (count(explode(' ', trim($field))) > 1) {
  700. list($fieldName, $fieldAlias) = explode(' ', trim($field));
  701. }
  702. if (empty($fieldAlias)) {
  703. $pos = strpos($fieldName, '.');
  704. $fieldAlias = substr($fieldName, ++$pos);
  705. }
  706. $return[$fieldAlias] = array(
  707. 'title' => ucwords(str_replace('_', ' ', $fieldAlias)),
  708. 'field' => $fieldName
  709. );
  710. }
  711. }
  712. $this->_queryParts['select'] = $return;
  713. return $this;
  714. }
  715. /**
  716. * Used to parse out the FROM and JOIN pieces of the DQL
  717. * and place it in the $_queryParts array for use
  718. * in many other places
  719. *
  720. * @return Bvb_Grid_Source_Doctrine
  721. */
  722. protected function _setFromParts()
  723. {
  724. $return = array();
  725. //Remove all 'as' instances
  726. $froms = $this->_removeAs($this->_query->getDqlPart('from'));
  727. foreach ($froms as $from) {
  728. $fields = explode(',', $from);
  729. $fields = array_map('trim', $fields);
  730. foreach ($fields as $field) {
  731. if (strpos(strtoupper($field), 'JOIN') === false) {
  732. $this->_queryParts = array_merge($this->_queryParts, $this->_explodeFrom($field));
  733. } else {
  734. $join = explode('JOIN', $field);
  735. $join = array_map('trim', $join);
  736. $joinType = strtolower($join[0]);
  737. $this->_queryParts = array_merge($this->_queryParts, $this->_explodeJoin($join[1], $joinType));
  738. }
  739. }
  740. }
  741. return $this;
  742. }
  743. /**
  744. * Used to set SELECT values to the DQL when
  745. * no SELECT is provided. We will just add
  746. * ALL columns for all tables given.
  747. *
  748. * NOTE: Since no SELECT was provided, to access these
  749. * from within the Bvb_Grid_Data class, you will need to
  750. * use the table alias + "_" + column name
  751. *
  752. * <code>
  753. * $grid->setGridColumns(array('co_code', 'co_name', 'co_continent', 'ci_name'));
  754. * </code>
  755. *
  756. * @return void
  757. */
  758. protected function _findAndSetSelect()
  759. {
  760. $return = array();
  761. //Make sure we have the FROM set
  762. $this->_setFromParts();
  763. $fromTableModel = $this->_queryParts['from']['tableModel'];
  764. $fromClass = Doctrine::getTable($fromTableModel);
  765. $fromColumns = array_keys($fromClass->getColumns());
  766. $fromAlias = $this->_queryParts['from']['alias'];
  767. foreach ($fromColumns as $fromColumn) {
  768. /**
  769. * Do this check here because a DQL with no JOIN,
  770. * does not need a table alias
  771. *
  772. * @var string
  773. */
  774. $addColumn = (!empty($fromAlias)) ? $fromAlias . '.' . $fromColumn : $fromColumn;
  775. $this->_query->addSelect($addColumn);
  776. }
  777. $joins = $this->_queryParts['join'];
  778. if (!empty($joins)) {
  779. foreach ($joins as $joinType) {
  780. foreach ($joinType as $join) {
  781. $joinClass = Doctrine::getTable($join['tableModel']);
  782. $joinColumns = array_keys($joinClass->getColumns());
  783. foreach ($joinColumns as $joinColumn) {
  784. $this->_query->addSelect($join['alias'] . '.' . $joinColumn);
  785. }
  786. }
  787. }
  788. }
  789. }
  790. /**
  791. * Take a DQL SELECT string and parse it into
  792. * a usable array
  793. *
  794. * <code>
  795. * $return = array(
  796. * 'from' => array(
  797. * 'alias' => 'c',
  798. * 'table' => 'country'
  799. * )
  800. * )
  801. * </code>
  802. *
  803. * @param string $from A DQL SELECT statement
  804. * @return array
  805. */
  806. private function _explodeFrom($from)
  807. {
  808. if (!is_string($from)) {
  809. require_once 'Bvb/Grid/Source/Doctrine/Exception.php';
  810. throw new Bvb_Grid_Source_Doctrine_Exception('Provided param needs to be a string only');
  811. }
  812. $return = array();
  813. $table = $from;
  814. $alias = null;
  815. if (count(explode(' ', $from)) > 1) {
  816. list($table, $alias) = explode(' ', $from);
  817. }
  818. if (strpos($table, '.') !== false) {
  819. $return = $this->_explodeJoin($from, 'left');
  820. } else {
  821. $return['from'] = array(
  822. 'alias' => $alias,
  823. 'tableModel' => $table,
  824. 'tableName' => Doctrine::getTable($table)->getTableName()
  825. );
  826. }
  827. return $return;
  828. }
  829. /**
  830. * Take a DQL JOIN string and parse it into
  831. * a usable array
  832. *
  833. * <code>
  834. * $return = array(
  835. * 'join' => array(
  836. * 'left' => array(
  837. * array(
  838. * 'alias' => 'ci'
  839. * 'table' => 'Model_City' //Doctrine Table name
  840. * 'tableAlias' => 'City' //What is used in the JOIN statement
  841. * 'joinOn' => 'c'
  842. * )
  843. * )
  844. * )
  845. * )
  846. * </code>
  847. *
  848. * @param string $join
  849. * @param string $joinType The type of join - LEFT, RIGHT, etc
  850. */
  851. private function _explodeJoin($join, $joinType)
  852. {
  853. $return = array();
  854. list($table, $alias) = explode(' ', $join);
  855. list($joinOn, $tableAlias) = explode('.', $table);
  856. $mainTable = $this->_getModelFromAlias($joinOn);
  857. $tableModel = Doctrine::getTable($mainTable)->getRelation($tableAlias)->getClass();
  858. $return['join'][strtolower($joinType)][] = array(
  859. 'alias' => $alias,
  860. 'tableModel' => $tableModel,
  861. 'tableAlias' => $tableAlias,
  862. 'joinOn' => $joinOn
  863. );
  864. return $return;
  865. }
  866. /**
  867. * Simple utility for removing "as" from the DQL
  868. *
  869. * @param string $subject
  870. * @return string No-As DQL expression
  871. */
  872. private function _removeAs($subject)
  873. {
  874. return str_replace(array(' AS', ' As', ' aS', ' as'), array('', '', '', ''), $subject);
  875. }
  876. /**
  877. * Find the table for which a column belongs
  878. *
  879. * @param string $column
  880. * @return string Name of the table used
  881. */
  882. private function _getModelFromColumn($column)
  883. {
  884. if (!is_string($column)) {
  885. $type = gettype($column);
  886. require_once 'Bvb/Grid/Source/Doctrine/Exception.php';
  887. throw new Bvb_Grid_Source_Doctrine_Exception('The $column param needs to be a string, ' . $type . ' provided');
  888. }
  889. if (empty($this->_queryParts['from']['alias'])) {
  890. return $this->_queryParts['from']['tableModel'];
  891. }
  892. list($alias, $field) = explode('.', $column);
  893. return $this->_getModelFromAlias($alias);
  894. }
  895. /**
  896. * Find the table/model based on the table alias provided
  897. *
  898. * @param string $alias
  899. * @return string
  900. */
  901. private function _getModelFromAlias($alias)
  902. {
  903. if (!is_string($alias)) {
  904. $type = gettype($alias);
  905. require_once 'Bvb/Grid/Source/Doctrine/Exception.php';
  906. throw new Bvb_Grid_Source_Doctrine_Exception('The $alias param needs to be a string, ' . $type . ' provided');
  907. }
  908. if ($this->_queryParts['from']['alias'] == $alias) {
  909. return $this->_queryParts['from']['tableModel'];
  910. }
  911. foreach ($this->_queryParts['join'] as $joins) {
  912. foreach ($joins as $join) {
  913. if ($join['alias'] == $alias) {
  914. return $join['tableModel'];
  915. }
  916. }
  917. }
  918. }
  919. /**
  920. * Find the table/model based on the table alias provided
  921. *
  922. * @param string $table Name of table to find model from
  923. * @return string Name of the model associated with the provided table
  924. */
  925. private function _getModelFromTable($table)
  926. {
  927. if (!is_string($table)) {
  928. $type = gettype($table);
  929. require_once 'Bvb/Grid/Source/Doctrine/Exception.php';
  930. throw new Bvb_Grid_Source_Doctrine_Exception('The $table param needs to be a string, ' . $type . ' provided');
  931. }
  932. if ($this->_queryParts['from']['tableName'] == $table) {
  933. return $this->_queryParts['from']['tableModel'];
  934. }
  935. foreach ($this->_queryParts['join'] as $joins) {
  936. foreach ($joins as $join) {
  937. if ($join['tableAlias'] == $table) {
  938. return $join['tableModel'];
  939. }
  940. }
  941. }
  942. }
  943. }