Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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.doctrine-project.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.doctrine-project.org
31
 * @since       1.0
32
 */
33
class Doctrine_Search extends Doctrine_Record_Generator
34
{
35
    const INDEX_FILES = 0;
36
 
37
    const INDEX_TABLES = 1;
38
 
39
    protected $_options = array('generateFiles'    => false,
40
                                'analyzer'         => 'Doctrine_Search_Analyzer_Standard',
41
                                'analyzer_options' => array(),
42
                                'type'             => self::INDEX_TABLES,
43
                                'className'        => '%CLASS%Index',
44
                                'generatePath'     => false,
45
                                'table'            => null,
46
                                'batchUpdates'     => false,
47
                                'pluginTable'      => false,
48
                                'fields'           => array(),
49
                                'connection'       => null,
50
                                'children'         => array(),
51
                                'cascadeDelete'    => true,
52
                                'appLevelDelete'   => false);
53
    /**
54
     * __construct
55
     *
56
     * @param array $options
57
     * @return void
58
     */
59
    public function __construct(array $options)
60
    {
61
        $this->_options = Doctrine_Lib::arrayDeepMerge($this->_options, $options);
62
 
63
        if ( ! isset($this->_options['analyzer'])) {
64
            $this->_options['analyzer'] = 'Doctrine_Search_Analyzer_Standard';
65
        }
66
 
67
        if ( ! isset($this->_options['analyzer_options'])) {
68
            $this->_options['analyzer_options'] = array();
69
        }
70
 
71
        $this->_options['analyzer'] = new $this->_options['analyzer']($this->_options['analyzer_options']);
72
    }
73
 
74
    public function buildTable()
75
    {
76
        $result = parent::buildTable();
77
 
78
        if ( ! isset($this->_options['connection'])) {
79
            $manager = Doctrine_Manager::getInstance();
80
            $this->_options['connection'] = $manager->getConnectionForComponent($this->_options['table']->getComponentName());
81
            $manager->bindComponent($this->_options['className'], $this->_options['connection']->getName());
82
        }
83
 
84
        return $result;
85
    }
86
 
87
    /**
88
     * Searchable keyword search
89
     *
90
     * @param string $string Keyword string to search for
91
     * @param Doctrine_Query $query Query object to alter. Adds where condition to limit the results using the search index
92
     * @return array    ids and relevancy
93
     */
94
    public function search($string, $query = null)
95
    {
96
        $q = new Doctrine_Search_Query($this->_table);
97
 
98
        if ($query instanceof Doctrine_Query) {
99
            $q->query($string, false);
100
 
101
            $newQuery = $query->copy();
102
            $query->getSqlQuery();
103
            $key = (array) $this->getOption('table')->getIdentifier();
104
            $newQuery->addWhere($query->getRootAlias() . '.'.current($key).' IN (SQL:' . $q->getSqlQuery() . ')', $q->getParams());
105
 
106
            return $newQuery;
107
        } else {
108
            if ( ! isset($this->_options['connection'])) {
109
                $this->_options['connection'] = $this->_table->getConnection();
110
            }
111
            $q->query($string);
112
            return $this->_options['connection']->fetchAll($q->getSqlQuery(), $q->getParams());
113
        }
114
    }
115
 
116
    /**
117
     * analyze a text in the encoding format
118
     *
119
     * @param string $text
120
     * @param string $encoding
121
     * @return void
122
     */
123
    public function analyze($text, $encoding = null)
124
    {
125
        return $this->_options['analyzer']->analyze($text, $encoding);
126
    }
127
 
128
    /**
129
     * updateIndex
130
     * updates the index
131
     *
132
     * @param Doctrine_Record $record
133
     * @return integer
134
     */
135
    public function updateIndex(array $data, $encoding = null)
136
    {
137
        $this->initialize($this->_options['table']);
138
 
139
        $fields = $this->getOption('fields');
140
        $class  = $this->getOption('className');
141
        $name   = $this->getOption('table')->getComponentName();
142
        $conn   = $this->getOption('table')->getConnection();
143
        $identifier = $this->_options['table']->getIdentifier();
144
 
145
        $q = Doctrine_Core::getTable($class)
146
            ->createQuery()
147
            ->delete();
148
        foreach ((array) $identifier as $id) {
149
            $q->addWhere($id . ' = ?', array($data[$id]));
150
        }
151
        $q->execute();
152
 
153
        if ($this->_options['batchUpdates'] === true) {
154
            $index = new $class();
155
 
156
            foreach ((array) $this->_options['table']->getIdentifier() as $id) {
157
                $index->$id = $data[$id];
158
            }
159
 
160
            $index->save();
161
        } else {
162
            foreach ($fields as $field) {
163
 
164
                $value = isset($data[$field]) ? $data[$field] : null;
165
 
166
                $terms = $this->analyze($value, $encoding);
167
 
168
                foreach ($terms as $pos => $term) {
169
                    $index = new $class();
170
 
171
                    $index->keyword = $term;
172
                    $index->position = $pos;
173
                    $index->field = $field;
174
                    foreach ((array) $this->_options['table']->getIdentifier() as $id) {
175
                        $index->$id = $data[$id];
176
                    }
177
 
178
                    $index->save();
179
                    $index->free(true);
180
                }
181
            }
182
        }
183
    }
184
 
185
    /**
186
     * readTableData
187
     *
188
     * @param mixed $limit
189
     * @param mixed $offset
190
     * @return Doctrine_Collection The collection of results
191
     */
192
    public function readTableData($limit = null, $offset = null)
193
    {
194
        $this->initialize($this->_options['table']);
195
 
196
        $conn      = $this->_options['table']->getConnection();
197
        $tableName = $this->_options['table']->getTableName();
198
        $id        = current($this->_options['table']->getIdentifierColumnNames());
199
        $tableId   = current($this->_table->getIdentifierColumnNames());
200
 
201
        $query = 'SELECT * FROM ' . $conn->quoteIdentifier($tableName)
202
               . ' WHERE ' . $conn->quoteIdentifier($id)
203
               . ' IN (SELECT ' . $conn->quoteIdentifier($tableId)
204
               . ' FROM ' . $conn->quoteIdentifier($this->_table->getTableName())
205
               . ' WHERE keyword = \'\') OR ' . $conn->quoteIdentifier($id)
206
               . ' NOT IN (SELECT ' . $conn->quoteIdentifier($tableId)
207
               . ' FROM ' . $conn->quoteIdentifier($this->_table->getTableName()) . ')';
208
 
209
        $query = $conn->modifyLimitQuery($query, $limit, $offset);
210
 
211
        return $conn->fetchAll($query);
212
    }
213
 
214
    /**
215
     * batchUpdateIndex
216
     *
217
     * @param mixed $limit
218
     * @param mixed $offset
219
     * @return void
220
     */
221
    public function batchUpdateIndex($limit = null, $offset = null, $encoding = null)
222
    {
223
        $table = $this->_options['table'];
224
 
225
        $this->initialize($table);
226
 
227
        $id        = $table->getIdentifierColumnNames();
228
        $class     = $this->_options['className'];
229
        $fields    = $this->_options['fields'];
230
        $conn      = $this->_options['table']->getConnection();
231
 
232
        for ($i = 0; $i < count($fields); $i++) {
233
            $fields[$i] = $table->getColumnName($fields[$i], $fields[$i]);
234
        }
235
 
236
        $rows = $this->readTableData($limit, $offset);
237
 
238
        $ids = array();
239
        foreach ($rows as $row) {
240
            foreach ($id as $idcol) {
241
                $ids[] = $row[$idcol];
242
            }
243
        }
244
 
245
        if (count($ids) > 0)
246
        {
247
            $sql = 'DELETE FROM ' . $conn->quoteIdentifier($this->_table->getTableName());
248
 
249
            if (count($id) == 1) {
250
                $placeholders = str_repeat('?, ', count($ids));
251
                $placeholders = substr($placeholders, 0, strlen($placeholders) - 2);
252
                $sql .= ' WHERE ' . $conn->quoteIdentifier($table->getIdentifier()) . ' IN (' . substr($placeholders, 0) . ')';
253
            } else {
254
                // composite primary key
255
                $placeholders = '';
256
                foreach ($table->getIdentifier() as $id) {
257
                    $placeholders .= $conn->quoteIdentifier($id) . ' = ? AND ';
258
                }
259
                $placeholders = '(' . substr($placeholders, 0, strlen($placeholders) - 5) . ') OR ';
260
                $placeholders = str_repeat($placeholders, count($rows));
261
                $placeholders = substr($placeholders, 0, strlen($placeholders) - 4);
262
                $sql .= ' WHERE ' . $placeholders;
263
            }
264
 
265
            $conn->exec($sql, $ids);
266
        }
267
 
268
        foreach ($rows as $row) {
269
            $conn->beginTransaction();
270
            try {
271
                foreach ($fields as $field) {
272
                    $data  = $row[$field];
273
 
274
                    $terms = $this->analyze($data, $encoding);
275
 
276
                    foreach ($terms as $pos => $term) {
277
                        $index = new $class();
278
 
279
                        $index->keyword = $term;
280
                        $index->position = $pos;
281
                        $index->field = $field;
282
 
283
                        foreach ((array) $table->getIdentifier() as $identifier) {
284
                            $index->$identifier = $row[$table->getColumnName($identifier, $identifier)];
285
                        }
286
 
287
                        $index->save();
288
                        $index->free(true);
289
                    }
290
                }
291
                $conn->commit();
292
            } catch (Doctrine_Exception $e) {
293
                $conn->rollback();
294
                throw $e;
295
            }
296
        }
297
    }
298
 
299
    /**
300
     * buildDefinition
301
     *
302
     * @return void
303
     */
304
    public function setTableDefinition()
305
    {
306
    	if ( ! isset($this->_options['table'])) {
307
    	    throw new Doctrine_Record_Exception("Unknown option 'table'.");
308
    	}
309
 
310
        $componentName = $this->_options['table']->getComponentName();
311
 
312
        $className = $this->getOption('className');
313
 
314
        $autoLoad = (bool) ($this->_options['generateFiles']);
315
        if (class_exists($className, $autoLoad)) {
316
            return false;
317
        }
318
 
319
        // move any columns currently in the primary key to the end
320
        // So that 'keyword' is the first field in the table
321
        $previousIdentifier = array();
322
        foreach ($this->_table->getIdentifier() as $name) {
323
            $previousIdentifier[$name] = $this->_table->getColumnDefinition($name);
324
            $this->_table->removeColumn($name);
325
        }
326
 
327
        $columns = array('keyword'  => array('type'    => 'string',
328
                                             'length'  => 200,
329
                                             'primary' => true,
330
                                             ),
331
                         'field'    => array('type'    => 'string',
332
                                             'length'  => 50,
333
                                             'primary' => true),
334
                         'position' => array('type'    => 'integer',
335
                                             'length'  => 8,
336
                                             'primary' => true,
337
                                             ));
338
 
339
        $this->hasColumns($columns);
340
        $this->hasColumns($previousIdentifier);
341
    }
342
}