# Coverage for Doctrine_Search_Query

Back to coverage report

1 | <?php
2 | /*
3 | * $Id: Hook.php 1939 2007-07-05 23:47:48Z zYne $
4 | *
5 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
6 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
7 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
8 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
9 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
11 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
12 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
13 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
14 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
15 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16 | *
17 | * This software consists of voluntary contributions made by many individuals
18 | * and is licensed under the LGPL. For more information, see
19 | * <http://www.phpdoctrine.org>.
20 | */
21 |
22 | /**
23 | * Doctrine_Search_Query
24 | *
25 | * @package Doctrine
26 | * @subpackage Search
27 | * @author Konsta Vesterinen <kvesteri@cc.hut.fi>
28 | * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
29 | * @version $Revision$
30 | * @link www.phpdoctrine.org
31 | * @since 1.0
32 | */
33 | class Doctrine_Search_Query
34 | {
35 |
36 | /**
37 | * @var Doctrine_Table $_table the index table
38 | */
39 | protected $_table = array();
40 |
41 | protected $_sql = '';
42 |
43 | protected $_params = array();
44 |
45 | protected $_words = array();
46 |
47 | protected $_tokenizer;
48 |
49 | protected $_condition;
50 |
51 | /**
52 | * @param Doctrine_Table $_table the index table
53 | */
54 | public function __construct($table)
55 | {
56 | if (is_string($table)) {
57 | $table = Doctrine_Manager::table($table);
58 | } else {
59 | if ( ! $table instanceof Doctrine_Table) {
60 | throw new Doctrine_Search_Exception('Invalid argument type. Expected instance of Doctrine_Table.');
61 | }
62 | }
63 |
64 | $this->_tokenizer = new Doctrine_Query_Tokenizer();
65 | $this->_table = $table;
66 |
67 | $foreignId = current(array_diff($this->_table->getColumnNames(), array('keyword', 'field', 'position')));
68 |
69 | $this->_condition = $foreignId . ' %s (SELECT ' . $foreignId . ' FROM ' . $this->_table->getTableName() . ' WHERE ';
70 | }
71 |
72 |
73 | public function query($text)
74 | {
75 | $text = trim($text);
76 |
77 | $foreignId = current(array_diff($this->_table->getColumnNames(), array('keyword', 'field', 'position')));
78 |
79 | $weighted = false;
80 | if (strpos($text, '^') === false) {
81 | $select = 'SELECT COUNT(keyword) AS relevance, ' . $foreignId;
82 | $from = 'FROM ' . $this->_table->getTableName();
83 | } else {
84 | // organize terms according weights
85 | $weighted = true;
86 |
87 | $select = 'SELECT SUM(sub_relevance) AS relevance, ' . $foreignId;
88 | $from = 'FROM ' ;
89 | }
90 |
91 | $where = 'WHERE ';
92 | $where .= $this->parseClause($text);
93 |
94 | $groupby = 'GROUP BY ' . $foreignId;
95 | $orderby = 'ORDER BY relevance DESC';
96 |
97 | $this->_sql = $select . ' ' . $from . ' ' . $where . ' ' . $groupby . ' ' . $orderby;
98 | }
99 |
100 | public function parseClause($originalClause, $recursive = false)
101 | {
102 | $clause = $this->_tokenizer->bracketTrim($originalClause);
103 |
104 | $brackets = false;
105 |
106 | if ($clause !== $originalClause) {
107 | $brackets = true;
108 | }
109 |
110 | $foreignId = current(array_diff($this->_table->getColumnNames(), array('keyword', 'field', 'position')));
111 |
112 | $terms = $this->_tokenizer->sqlExplode($clause, ' OR ', '(', ')');
113 |
114 | $ret = array();
115 |
116 | if (count($terms) > 1) {
117 | $leavesOnly = true;
118 |
119 | foreach ($terms as $k => $term) {
120 | if ($this->isExpression($term)) {
121 | $ret[$k] = $this->parseClause($term, true);
122 | $leavesOnly = false;
123 | } else {
124 | $ret[$k] = $this->parseTerm($term);
125 | }
126 | }
127 |
128 | $return = implode(' OR ', $ret);
129 |
130 | if ($leavesOnly && $recursive) {
131 | $return = sprintf($this->_condition, 'IN') . $return . ')';
132 | $brackets = false;
133 | }
134 | } else {
135 | $terms = $this->_tokenizer->sqlExplode($clause, ' ', '(', ')');
136 |
137 | if (count($terms) === 1 && ! $recursive) {
138 | $return = $this->parseTerm($clause);
139 | } else {
140 | foreach ($terms as $k => $term) {
141 | $term = trim($term);
142 |
143 | if ($term === 'AND') {
144 | continue;
145 | }
146 |
147 | if (substr($term, 0, 1) === '-') {
148 | $operator = 'NOT IN';
149 | $term = substr($term, 1);
150 | } else {
151 | $operator = 'IN';
152 | }
153 |
154 | if ($this->isExpression($term)) {
155 | $ret[$k] = $this->parseClause($term, true);
156 | } else {
157 | $ret[$k] = sprintf($this->_condition, $operator) . $this->parseTerm($term) . ')';
158 | }
159 | }
160 | $return = implode(' AND ', $ret);
161 | }
162 | }
163 |
164 | if ($brackets) {
165 | return '(' . $return . ')';
166 | } else {
167 | return $return;
168 | }
169 | }
170 | public function isExpression($term)
171 | {
172 | if (strpos($term, '(') !== false) {
173 | return true;
174 | } else {
175 | $terms = $this->_tokenizer->quoteExplode($term);
176 |
177 | return (count($terms) > 1);
178 | }
179 | }
180 |
181 | public function parseTerm($term)
182 | {
183 | $negation = false;
184 |
185 | if (strpos($term, "'") === false) {
186 | $where = $this->parseWord($term);
187 | } else {
188 | $term = trim($term, "' ");
189 |
190 | $terms = $this->_tokenizer->quoteExplode($term);
191 | $where = $this->parseWord($terms[0]);
192 |
193 | foreach ($terms as $k => $word) {
194 | if ($k === 0) {
195 | continue;
196 | }
197 | $where .= ' AND (position + ' . $k . ') = (SELECT position FROM ' . $this->_table->getTableName() . ' WHERE ' . $this->parseWord($word) . ')';
198 | }
199 | }
200 | return $where;
201 | }
202 | public function parseWord($word)
203 | {
204 | $this->_words[] = str_replace('*', '', $word);
205 |
206 | if (strpos($word, '?') !== false ||
207 | strpos($word, '*') !== false) {
208 |
209 | $word = str_replace('*', '%', $word);
210 |
211 | $where = 'keyword LIKE ?';
212 |
213 | $params = array($word);
214 | } else {
215 | $where = 'keyword = ?';
216 | }
217 |
218 | $this->_params[] = $word;
219 |
220 | return $where;
221 | }
222 |
223 | public function getWords()
224 | {
225 | return $this->_words;
226 | }
227 | public function getParams()
228 | {
229 | return $this->_params;
230 | }
231 | public function getSql()
232 | {
233 | return $this->_sql;
234 | }
235 | }