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.com>.
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.com
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
46     protected $_condition;
47
48     /**
49      * @param Doctrine_Table $_table        the index table
50      */
51     public function __construct($table)
52     {
53         if (is_string($table)) {
54            $table = Doctrine_Manager::table($table);
55         } else {
56             if ( ! $table instanceof Doctrine_Table) {
57                 throw new Doctrine_Search_Exception('Invalid argument type. Expected instance of Doctrine_Table.');
58             }
59         }
60
61         $this->_table = $table;
62
63         $foreignId = current(array_diff($this->_table->getColumnNames(), array('keyword', 'field', 'position')));
64
65         $this->_condition = $foreignId . ' %s (SELECT ' . $foreignId . ' FROM ' . $this->_table->getTableName() . ' WHERE ';
66     }
67
68
69     public function query($text)
70     {
71         $text = trim($text);
72
73         $foreignId = current(array_diff($this->_table->getColumnNames(), array('keyword', 'field', 'position')));
74
75         $weighted = false;
76         if (strpos($text, '^') === false) {
77             $select = 'SELECT COUNT(keyword) AS relevance, ' . $foreignId;
78             $from = 'FROM ' . $this->_table->getTableName();
79         } else {
80             // organize terms according weights
81             $weighted = true;
82
83             $select = 'SELECT SUM(sub_relevance) AS relevance, ' . $foreignId;
84             $from = 'FROM ' ;
85         }
86         
87         $where = 'WHERE ';
88         $where .= $this->parseClause($text);
89
90         $groupby = 'GROUP BY ' . $foreignId;
91         $orderby = 'ORDER BY relevance';
92
93         $this->_sql = $select . ' ' . $from . ' ' . $where . ' ' . $groupby . ' ' . $orderby;
94     }
95
96     public function parseClause($originalClause, $recursive = false)
97     {
98         $clause = Doctrine_Tokenizer::bracketTrim($originalClause);
99         
100         $brackets = false;
101
102         if ($clause !== $originalClause) {
103             $brackets = true;
104         }
105
106         $foreignId = current(array_diff($this->_table->getColumnNames(), array('keyword', 'field', 'position')));
107         
108         $terms = Doctrine_Tokenizer::sqlExplode($clause, ' OR ', '(', ')');
109
110         $ret = array();
111
112         if (count($terms) > 1) {
113             $leavesOnly = true;
114
115             foreach ($terms as $k => $term) {
116                 if ($this->isExpression($term)) {
117                     $ret[$k] = $this->parseClause($term, true);
118                     $leavesOnly = false;
119                 } else {
120                     $ret[$k] = $this->parseTerm($term);
121                 }
122             }
123
124             $return = implode(' OR ', $ret);
125
126             if ($leavesOnly && $recursive) {
127                 $return = sprintf($this->_condition, 'IN') . $return . ')';
128                 $brackets = false;
129             }
130         } else {
131             $terms = Doctrine_Tokenizer::sqlExplode($clause, ' ', '(', ')');
132             
133             if (count($terms) === 1 && ! $recursive) {
134                 $return = $this->parseTerm($clause);
135             } else {
136                 foreach ($terms as $k => $term) {
137                     $term = trim($term);
138     
139                     if ($term === 'AND') {
140                         continue;
141                     }
142     
143                     if (substr($term, 0, 1) === '-') {
144                         $operator = 'NOT IN';
145                         $term = substr($term, 1);
146                     } else {
147                         $operator = 'IN';
148                     }
149     
150                     if ($this->isExpression($term)) {
151                         $ret[$k] = $this->parseClause($term, true);
152                     } else {
153                         $ret[$k] = sprintf($this->_condition, $operator) . $this->parseTerm($term) . ')';
154                     }
155                 }
156                 $return = implode(' AND ', $ret);
157             }
158         }
159
160         if ($brackets) {
161             return '(' . $return . ')';
162         } else {
163             return $return;
164         }
165     }
166     public function isExpression($term)
167     {
168         if (strpos($term, '(') !== false) {
169             return true;
170         } else {
171             $terms = Doctrine_Tokenizer::quoteExplode($term);
172             
173             return (count($terms) > 1);
174         }
175     }
176
177     public function parseTerm($term)
178     {
179         $negation = false;
180
181         if (strpos($term, "'") === false) {
182             $where = $this->parseWord($term);
183         } else {
184             $term = trim($term, "' ");
185
186             $terms = Doctrine_Tokenizer::quoteExplode($term);
187             $where = $this->parseWord($terms[0]);
188
189             foreach ($terms as $k => $word) {
190                 if ($k === 0) {
191                     continue;
192                 }
193                 $where .= ' AND (position + ' . $k . ') = (SELECT position FROM ' . $this->_table->getTableName() . ' WHERE ' . $this->parseWord($word) . ')';
194             }
195         }
196         return $where;
197     }
198     public function parseWord($word) 
199     {
200         if (strpos($word, '?') !== false ||
201             strpos($word, '*') !== false) {
202             
203             $word = str_replace('*', '%', $word);
204
205             $where = 'keyword LIKE ?';
206             
207             $params = array($word);
208         } else {
209             $where = 'keyword = ?';
210         }
211         
212         $this->_params[] = $word;
213
214         return $where;
215     }
216     public function getParams()
217     {
218         return $this->_params;
219     }
220     public function getSql()
221     {
222         return $this->_sql;
223     }
224 }