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 }