# Coverage for Doctrine_Search

Back to coverage report

1 | <?php
2 | /*
3 | * $Id$
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
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 extends Doctrine_Plugin
34 | {
35 | const INDEX_FILES = 0;
36 |
37 | const INDEX_TABLES = 1;
38 |
39 | protected $_options = array('generateFiles' => false,
40 | 'type' => self::INDEX_TABLES,
41 | 'className' => '%CLASS%Index',
42 | 'generatePath' => false,
43 | 'resource' => null,
44 | 'batchUpdates' => false,
45 | 'pluginTable' => false,
46 | 'fields' => array(),
47 | 'connection' => null);
48 |
49 | protected $_built = false;
50 |
51 |
52 | /**
53 | * __construct
54 | *
55 | * @param array $options
56 | * @return void
57 | */
58 | public function __construct(array $options)
59 | {
60 | $this->_options = array_merge($this->_options, $options);
61 |
62 | if ( ! isset($this->_options['analyzer'])) {
63 | $this->_options['analyzer'] = new Doctrine_Search_Analyzer_Standard();
64 | }
65 | if ( ! isset($this->_options['connection'])) {
66 | $this->_options['connection'] = Doctrine_Manager::connection();
67 | }
68 | }
69 |
70 |
71 | /**
72 | * search
73 | *
74 | * @param string $query
75 | * @return Doctrine_Collection The collection of search results
76 | */
77 | public function search($query)
78 | {
79 | $q = new Doctrine_Search_Query($this->_options['pluginTable']);
80 |
81 | $q->query($query);
82 |
83 | return $this->_options['connection']->fetchAll($q->getSql(), $q->getParams());;
84 | }
85 |
86 | /**
87 | * analyze
88 | *
89 | * @param string $text
90 | * @return void
91 | */
92 | public function analyze($text)
93 | {
94 | return $this->_options['analyzer']->analyze($text);
95 | }
96 |
97 | /**
98 | * updateIndex
99 | * updates the index
100 | *
101 | * @param Doctrine_Record $record
102 | * @return integer
103 | */
104 | public function updateIndex(array $data)
105 | {
106 | $this->buildDefinition();
107 |
108 | $fields = $this->getOption('fields');
109 | $class = $this->getOption('className');
110 | $name = $this->getOption('resource')->getComponentName();
111 | $conn = $this->getOption('resource')->getConnection();
112 | $identifier = $this->_options['resource']->getIdentifier();
113 |
114 | $q = Doctrine_Query::create()->delete()
115 | ->from($class);
116 | foreach ((array) $identifier as $id) {
117 | $q->addWhere($id . ' = ?', array($data[$id]));
118 | }
119 | $q->execute();
120 |
121 | if ($this->_options['batchUpdates'] === true) {
122 | $index = new $class();
123 |
124 | foreach ((array) $this->_options['resource']->getIdentifier() as $id) {
125 | $index->$id = $data[$id];
126 | }
127 |
128 | $index->save();
129 | } else {
130 | foreach ($fields as $field) {
131 |
132 | $value = $data[$field];
133 |
134 | $terms = $this->analyze($value);
135 |
136 | foreach ($terms as $pos => $term) {
137 | $index = new $class();
138 |
139 | $index->keyword = $term;
140 | $index->position = $pos;
141 | $index->field = $field;
142 | foreach ((array) $this->_options['resource']->getIdentifier() as $id) {
143 | $index->$id = $data[$id];
144 | }
145 |
146 | $index->save();
147 | }
148 | }
149 | }
150 | }
151 |
152 | /**
153 | * readTableData
154 | *
155 | * @param mixed $limit
156 | * @param mixed $offset
157 | * @return Doctrine_Collection The collection of results
158 | */
159 | public function readTableData($limit = null, $offset = null)
160 | {
161 | $this->buildDefinition();
162 |
163 | $conn = $this->_options['resource']->getConnection();
164 | $tableName = $this->_options['resource']->getTableName();
165 | $id = $this->_options['resource']->getIdentifier();
166 |
167 | $query = 'SELECT * FROM ' . $conn->quoteIdentifier($tableName)
168 | . ' WHERE ' . $conn->quoteIdentifier($id)
169 | . ' IN (SELECT ' . $conn->quoteIdentifier($id)
170 | . ' FROM ' . $conn->quoteIdentifier($this->_options['pluginTable']->getTableName())
171 | . ' WHERE keyword IS NULL)';
172 |
173 | $query = $conn->modifyLimitQuery($query, $limit, $offset);
174 |
175 | return $conn->fetchAll($query);
176 | }
177 |
178 |
179 |
180 | /**
181 | * batchUpdateIndex
182 | *
183 | * @param mixed $limit
184 | * @param mixed $offset
185 | * @return void
186 | */
187 | public function batchUpdateIndex($limit = null, $offset = null)
188 | {
189 | $this->buildDefinition();
190 |
191 | $id = $this->_options['resource']->getIdentifier();
192 | $class = $this->_options['className'];
193 | $fields = $this->_options['fields'];
194 | $conn = $this->_options['connection'];
195 | try {
196 |
197 | $conn->beginTransaction();
198 |
199 | $rows = $this->readTableData($limit, $offset);
200 |
201 | foreach ($rows as $row) {
202 | $ids[] = $row[$id];
203 | }
204 |
205 | $conn->exec('DELETE FROM '
206 | . $conn->quoteIdentifier($this->_options['pluginTable']->getTableName())
207 | . ' WHERE ' . $conn->quoteIdentifier($id) . ' IN (' . implode(', ', $ids) . ')');
208 |
209 | foreach ($rows as $row) {
210 | foreach ($fields as $field) {
211 | $data = $row[$field];
212 |
213 | $terms = $this->analyze($data);
214 |
215 | foreach ($terms as $pos => $term) {
216 | $index = new $class();
217 |
218 | $index->keyword = $term;
219 | $index->position = $pos;
220 | $index->field = $field;
221 |
222 | foreach ((array) $id as $identifier) {
223 | $index->$identifier = $row[$identifier];
224 | }
225 |
226 | $index->save();
227 | }
228 | }
229 | }
230 |
231 | $conn->commit();
232 | } catch (Doctrine_Exception $e) {
233 | $conn->rollback();
234 | }
235 | }
236 |
237 | /**
238 | * buildDefinition
239 | *
240 | * @return void
241 | */
242 | public function buildDefinition()
243 | {
244 | if ($this->_built) {
245 | return true;
246 | }
247 | $this->_built = true;
248 |
249 | $componentName = $this->_options['resource']->getComponentName();
250 |
251 | // check for placeholders
252 | if (strpos($this->_options['className'], '%') !== false) {
253 | $this->_options['className'] = str_replace('%CLASS%', $componentName, $this->_options['className']);
254 | }
255 |
256 | $className = $this->getOption('className');
257 |
258 | if (class_exists($className)) {
259 | return false;
260 | }
261 |
262 | $columns = array('keyword' => array('type' => 'string',
263 | 'length' => 200,
264 | 'primary' => true,
265 | ),
266 | 'field' => array('type' => 'string',
267 | 'length' => 50,
268 | 'primary' => true),
269 | 'position' => array('type' => 'integer',
270 | 'length' => 8,
271 | 'primary' => true,
272 | ));
273 |
274 | $id = $this->_options['resource']->getIdentifier();
275 |
276 | $options = array('className' => $className);
277 |
278 | $fk = $this->generateForeignKeys($this->_options['resource']);
279 | $columns += $fk;
280 |
281 | $relations = array();
282 | // only generate relations for database based searches
283 | if ( ! $this instanceof Doctrine_Search_File) {
284 | $relations = $this->generateRelation($this->_options['resource'], $fk);
285 | }
286 |
287 | $this->generateClass($options, $columns, $relations);
288 |
289 | $this->_options['pluginTable'] = $this->_options['connection']->getTable($this->_options['className']);
290 |
291 | return true;
292 | }
293 | }