. */ /** * Doctrine_Search_Query * * @package Doctrine * @license http://www.opensource.org/licenses/lgpl-license.php LGPL * @category Object Relational Mapping * @link www.phpdoctrine.com * @since 1.0 * @version $Revision: 1939 $ * @author Konsta Vesterinen */ class Doctrine_Search_Query { const OPERATOR_OR = 0; const OPERATOR_AND = 1; /** * @var Doctrine_Query $query the base query */ protected $_query; /** * @var Doctrine_Table $_table the index table */ protected $_table = array(); protected $_sql = ''; /** * @param octrine_Table $_table the index table */ public function __construct($table) { if (is_string($table)) { $table = Doctrine_Manager::table($table); } $this->_table = $table; $this->_query = new Doctrine_Query(); } /** * getQuery * * @return Doctrine_Query returns the query object associated with this object */ public function getQuery() { return $this->_query; } public function search($text) { $text = strtolower(trim($text)); $terms = Doctrine_Tokenizer::sqlExplode($text, ' AND ', '(', ')'); $foreignId = current(array_diff($this->_table->getColumnNames(), array('keyword', 'field', 'position'))); $numTerms = count($terms); $weighted = false; if (strpos($text, '^') === false) { $select = 'SELECT COUNT(keyword) AS relevance, ' . $foreignId; $from = 'FROM ' . $this->_table->getTableName(); } else { // organize terms according weights foreach ($terms as $k => $term) { $e = explode('^', $term); $x = (isset($e[1]) && is_numeric($e[1])) ? $e[1] : 1; $weightedTerms[$x][] = $term; } $weighted = true; $select = 'SELECT SUM(sub_relevance) AS relevance, ' . $foreignId; $from = 'FROM ' ; } switch ($numTerms) { case 0: return false; break; case 1: // only one term found, use fast and simple query $data = $this->parseTerm($terms[0]); $where = $data[0]; $params = $data[1]; break; default: $where = 'WHERE '; $cond = array(); $params = array(); foreach ($terms as $term) { $data = $this->parseTerm($term); $params = array_merge($params, $data[1]); $cond[] = $foreignId . ' IN (SELECT ' . $foreignId . ' FROM ' . $this->_table->getTableName() . ' ' . $data[0] . ')'; } $where .= implode(' AND ', $cond); } $groupby = 'GROUP BY ' . $foreignId; $orderby = 'ORDER BY relevance'; $this->_sql = $select . ' ' . $from . ' ' . $where . ' ' . $groupby . ' ' . $orderby; } public function tokenizeClause($clause) { $clause = Doctrine_Tokenizer::bracketTrim($clause); $terms = Doctrine_Tokenizer::sqlExplode($clause, ' ', '(', ')'); $operator = self::OPERATOR_AND; $ret = array(); $pending = false; $i = 0; $prev = false; foreach ($terms as $k => $term) { $term = trim($term); if ($term === 'AND') { $operator = self::OPERATOR_AND; } elseif ($term === 'OR') { $operator = self::OPERATOR_OR; } else { if ($operator === self::OPERATOR_OR) { if ( ! is_array($ret[($i-1)])) { $ret[($i-1)] = array($ret[($i-1)], $term); } else { $ret[($i-1)][] = $term; } } else { $ret[$i] = $term; $i++; } $operator = self::OPERATOR_AND; } } return $ret; } public function parseClause($clause) { $clause = Doctrine_Tokenizer::bracketTrim($clause); $foreignId = current(array_diff($this->_table->getColumnNames(), array('keyword', 'field', 'position'))); $terms = $this->tokenizeClause($clause); if (count($terms) > 1) { $ret = array(); foreach ($terms as $term) { if (is_array($term)) { $parsed = $this->parseTerms($term); } else { if (strpos($term, '(') === false) { $parsed = $foreignId . ' IN (SELECT ' . $foreignId . ' FROM ' . $this->_table->getTableName() . ' WHERE ' . $this->parseClause($term) . ')'; } else { $parsed = $this->parseClause($term); } } if (strlen($parsed) > 20) { $ret[] = '(' . $parsed . ')'; } else { $ret[] = $parsed; } } $r = implode(' AND ', $ret); } else { $terms = (is_array($terms[0])) ? $terms[0] : array($terms[0]); return $this->parseTerms($terms); } return $r; } public function parseTerms(array $terms) { $foreignId = current(array_diff($this->_table->getColumnNames(), array('keyword', 'field', 'position'))); if (count($terms) > 1) { $ret = array(); foreach ($terms as $term) { $ret[] = $this->parseClause($term); } $parsed = implode(' OR ', $ret); if (strpos($parsed, '(') === false) { $parsed = $foreignId . ' IN (SELECT ' . $foreignId . ' FROM ' . $this->_table->getTableName() . ' WHERE ' . $parsed . ')'; } return $parsed; } else { $ret = $this->parseTerm($terms[0]); return $ret[0]; } } public function parseTerm($term) { if (strpos($term, "'") === false) { $where = 'keyword = ?'; $params = array($term); } else { $term = trim($term, "' "); $where = 'keyword = ?'; $terms = Doctrine_Tokenizer::quoteExplode($term); $params = $terms; foreach ($terms as $k => $word) { if ($k === 0) { continue; } $where .= ' AND (position + ' . $k . ') = (SELECT position FROM ' . $this->_table->getTableName() . ' WHERE keyword = ?)'; } } return array($where, $params); } public function getSql() { return $this->_sql; } public function execute() { $resultSet = $this->_query->execute(); return $resultSet; } }