optimize()   F
last analyzed

Complexity

Conditions 50
Paths > 20000

Size

Total Lines 287
Code Lines 144

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 50
Metric Value
cc 50
eloc 144
nc 429496.7295
nop 1
dl 0
loc 287
ccs 24
cts 24
cp 1
crap 50
rs 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Zend Framework
4
 *
5
 * LICENSE
6
 *
7
 * This source file is subject to the new BSD license that is bundled
8
 * with this package in the file LICENSE.txt.
9
 * It is also available through the world-wide-web at this URL:
10
 * http://framework.zend.com/license/new-bsd
11
 * If you did not receive a copy of the license and are unable to
12
 * obtain it through the world-wide-web, please send an email
13
 * to [email protected] so we can send you a copy immediately.
14
 *
15
 * @category   Zend
16
 * @package    Zend_Search_Lucene
17
 * @subpackage Search
18
 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
19
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
20
 * @version    $Id: Boolean.php 24593 2012-01-05 20:35:02Z matthew $
21
 */
22
23
24
/** Zend_Search_Lucene_Search_Query */
25 1
require_once 'Zend/Search/Lucene/Search/Query.php';
26
27
28
/**
29
 * @category   Zend
30
 * @package    Zend_Search_Lucene
31
 * @subpackage Search
32
 * @copyright  Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
33
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
34
 */
35
class Zend_Search_Lucene_Search_Query_Boolean extends Zend_Search_Lucene_Search_Query
36
{
37
38
    /**
39
     * Subqueries
40
     * Array of Zend_Search_Lucene_Search_Query
41
     *
42
     * @var array
43
     */
44
    private $_subqueries = array();
45
46
    /**
47
     * Subqueries signs.
48
     * If true then subquery is required.
49
     * If false then subquery is prohibited.
50
     * If null then subquery is neither prohibited, nor required
51
     *
52
     * If array is null then all subqueries are required
53
     *
54
     * @var array
55
     */
56
    private $_signs = array();
57
58
    /**
59
     * Result vector.
60
     *
61
     * @var array
62
     */
63
    private $_resVector = null;
64
65
    /**
66
     * A score factor based on the fraction of all query subqueries
67
     * that a document contains.
68
     * float for conjunction queries
69
     * array of float for non conjunction queries
70
     *
71
     * @var mixed
72
     */
73
    private $_coord = null;
74
75
76
    /**
77
     * Class constructor.  Create a new Boolean query object.
78
     *
79
     * if $signs array is omitted then all subqueries are required
80
     * it differs from addSubquery() behavior, but should never be used
81
     *
82
     * @param array $subqueries    Array of Zend_Search_Search_Query objects
83
     * @param array $signs    Array of signs.  Sign is boolean|null.
84
     * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
85
     */
86 1
    public function __construct($subqueries = null, $signs = null)
87
    {
88 1
        if (is_array($subqueries)) {
89
            $this->_subqueries = $subqueries;
90
91
            $this->_signs = null;
92
            // Check if all subqueries are required
93
            if (is_array($signs)) {
94
                foreach ($signs as $sign ) {
95
                    if ($sign !== true) {
96
                        $this->_signs = $signs;
97
                        break;
98
                    }
99
                }
100
            }
101
        }
102 1
    }
103
104
105
    /**
106
     * Add a $subquery (Zend_Search_Lucene_Search_Query) to this query.
107
     *
108
     * The sign is specified as:
109
     *     TRUE  - subquery is required
110
     *     FALSE - subquery is prohibited
111
     *     NULL  - subquery is neither prohibited, nor required
112
     *
113
     * @param  Zend_Search_Lucene_Search_Query $subquery
114
     * @param  boolean|null $sign
115
     * @return void
116
     */
117 1
    public function addSubquery(Zend_Search_Lucene_Search_Query $subquery, $sign=null) {
118 1
        if ($sign !== true || $this->_signs !== null) {       // Skip, if all subqueries are required
119 1
            if ($this->_signs === null) {                     // Check, If all previous subqueries are required
120
                $this->_signs = array();
121
                foreach ($this->_subqueries as $prevSubquery) {
122
                    $this->_signs[] = true;
123
                }
124
            }
125 1
            $this->_signs[] = $sign;
126
        }
127
128 1
        $this->_subqueries[] = $subquery;
129 1
    }
130
131
    /**
132
     * Re-write queries into primitive queries
133
     *
134
     * @param Zend_Search_Lucene_Interface $index
135
     * @return Zend_Search_Lucene_Search_Query
136
     */
137 1
    public function rewrite(Zend_Search_Lucene_Interface $index)
138
    {
139 1
        $query = new Zend_Search_Lucene_Search_Query_Boolean();
140 1
        $query->setBoost($this->getBoost());
141
142 1
        foreach ($this->_subqueries as $subqueryId => $subquery) {
143 1
            $query->addSubquery($subquery->rewrite($index),
144 1
                                ($this->_signs === null)?  true : $this->_signs[$subqueryId]);
145
        }
146
147 1
        return $query;
148
    }
149
150
    /**
151
     * Optimize query in the context of specified index
152
     *
153
     * @param Zend_Search_Lucene_Interface $index
154
     * @return Zend_Search_Lucene_Search_Query
155
     */
156 1
    public function optimize(Zend_Search_Lucene_Interface $index)
157
    {
158 1
        $subqueries = array();
159 1
        $signs      = array();
160
161
        // Optimize all subqueries
162 1
        foreach ($this->_subqueries as $id => $subquery) {
163 1
            $subqueries[] = $subquery->optimize($index);
164 1
            $signs[]      = ($this->_signs === null)? true : $this->_signs[$id];
165
        }
166
167
        // Remove insignificant subqueries
168 1
        foreach ($subqueries as $id => $subquery) {
169 1
            if ($subquery instanceof Zend_Search_Lucene_Search_Query_Insignificant) {
170
                // Insignificant subquery has to be removed anyway
171
                unset($subqueries[$id]);
172 1
                unset($signs[$id]);
173
            }
174
        }
175 1
        if (count($subqueries) == 0) {
176
            // Boolean query doesn't has non-insignificant subqueries
177
            require_once 'Zend/Search/Lucene/Search/Query/Insignificant.php';
178
            return new Zend_Search_Lucene_Search_Query_Insignificant();
179
        }
180
        // Check if all non-insignificant subqueries are prohibited
181 1
        $allProhibited = true;
182 1
        foreach ($signs as $sign) {
183 1
            if ($sign !== false) {
184 1
                $allProhibited = false;
185 1
                break;
186
            }
187
        }
188 1
        if ($allProhibited) {
189
            require_once 'Zend/Search/Lucene/Search/Query/Insignificant.php';
190
            return new Zend_Search_Lucene_Search_Query_Insignificant();
191
        }
192
193
194
        // Check for empty subqueries
195 1
        foreach ($subqueries as $id => $subquery) {
196 1
            if ($subquery instanceof Zend_Search_Lucene_Search_Query_Empty) {
197 1
                if ($signs[$id] === true) {
198
                    // Matching is required, but is actually empty
199
                    require_once 'Zend/Search/Lucene/Search/Query/Empty.php';
200
                    return new Zend_Search_Lucene_Search_Query_Empty();
201
                } else {
202
                    // Matching is optional or prohibited, but is empty
203
                    // Remove it from subqueries and signs list
204 1
                    unset($subqueries[$id]);
205 1
                    unset($signs[$id]);
206
                }
207
            }
208
        }
209
210
        // Check, if reduced subqueries list is empty
211 1
        if (count($subqueries) == 0) {
212 1
            require_once 'Zend/Search/Lucene/Search/Query/Empty.php';
213 1
            return new Zend_Search_Lucene_Search_Query_Empty();
214
        }
215
216
        // Check if all non-empty subqueries are prohibited
217
        $allProhibited = true;
218
        foreach ($signs as $sign) {
219
            if ($sign !== false) {
220
                $allProhibited = false;
221
                break;
222
            }
223
        }
224
        if ($allProhibited) {
225
            require_once 'Zend/Search/Lucene/Search/Query/Empty.php';
226
            return new Zend_Search_Lucene_Search_Query_Empty();
227
        }
228
229
230
        // Check, if reduced subqueries list has only one entry
231
        if (count($subqueries) == 1) {
232
            // It's a query with only one required or optional clause
233
            // (it's already checked, that it's not a prohibited clause)
234
235
            if ($this->getBoost() == 1) {
236
                return reset($subqueries);
237
            }
238
239
            $optimizedQuery = clone reset($subqueries);
240
            $optimizedQuery->setBoost($optimizedQuery->getBoost()*$this->getBoost());
241
242
            return $optimizedQuery;
243
        }
244
245
246
        // Prepare first candidate for optimized query
247
        $optimizedQuery = new Zend_Search_Lucene_Search_Query_Boolean($subqueries, $signs);
248
        $optimizedQuery->setBoost($this->getBoost());
249
250
251
        $terms        = array();
252
        $tsigns       = array();
253
        $boostFactors = array();
254
255
        // Try to decompose term and multi-term subqueries
256
        foreach ($subqueries as $id => $subquery) {
257
            if ($subquery instanceof Zend_Search_Lucene_Search_Query_Term) {
258
                $terms[]        = $subquery->getTerm();
259
                $tsigns[]       = $signs[$id];
260
                $boostFactors[] = $subquery->getBoost();
261
262
                // remove subquery from a subqueries list
263
                unset($subqueries[$id]);
264
                unset($signs[$id]);
265
           } else if ($subquery instanceof Zend_Search_Lucene_Search_Query_MultiTerm) {
266
                $subTerms = $subquery->getTerms();
267
                $subSigns = $subquery->getSigns();
268
269
                if ($signs[$id] === true) {
270
                    // It's a required multi-term subquery.
271
                    // Something like '... +(+term1 -term2 term3 ...) ...'
272
273
                    // Multi-term required subquery can be decomposed only if it contains
274
                    // required terms and doesn't contain prohibited terms:
275
                    // ... +(+term1 term2 ...) ... => ... +term1 term2 ...
276
                    //
277
                    // Check this
278
                    $hasRequired   = false;
279
                    $hasProhibited = false;
280
                    if ($subSigns === null) {
281
                        // All subterms are required
282
                        $hasRequired = true;
283
                    } else {
284
                        foreach ($subSigns as $sign) {
285
                            if ($sign === true) {
286
                                $hasRequired   = true;
287
                            } else if ($sign === false) {
288
                                $hasProhibited = true;
289
                                break;
290
                            }
291
                        }
292
                    }
293
                    // Continue if subquery has prohibited terms or doesn't have required terms
294
                    if ($hasProhibited  ||  !$hasRequired) {
295
                        continue;
296
                    }
297
298
                    foreach ($subTerms as $termId => $term) {
299
                        $terms[]        = $term;
300
                        $tsigns[]       = ($subSigns === null)? true : $subSigns[$termId];
301
                        $boostFactors[] = $subquery->getBoost();
302
                    }
303
304
                    // remove subquery from a subqueries list
305
                    unset($subqueries[$id]);
306
                    unset($signs[$id]);
307
308
                } else { // $signs[$id] === null  ||  $signs[$id] === false
309
                    // It's an optional or prohibited multi-term subquery.
310
                    // Something like '... (+term1 -term2 term3 ...) ...'
311
                    // or
312
                    // something like '... -(+term1 -term2 term3 ...) ...'
313
314
                    // Multi-term optional and required subqueries can be decomposed
315
                    // only if all terms are optional.
316
                    //
317
                    // Check if all terms are optional.
318
                    $onlyOptional = true;
319
                    if ($subSigns === null) {
320
                        // All subterms are required
321
                        $onlyOptional = false;
322
                    } else {
323
                        foreach ($subSigns as $sign) {
324
                            if ($sign !== null) {
325
                                $onlyOptional = false;
326
                                break;
327
                            }
328
                        }
329
                    }
330
331
                    // Continue if non-optional terms are presented in this multi-term subquery
332
                    if (!$onlyOptional) {
333
                        continue;
334
                    }
335
336
                    foreach ($subTerms as $termId => $term) {
337
                        $terms[]  = $term;
338
                        $tsigns[] = ($signs[$id] === null)? null  /* optional */ :
339
                                                            false /* prohibited */;
340
                        $boostFactors[] = $subquery->getBoost();
341
                    }
342
343
                    // remove subquery from a subqueries list
344
                    unset($subqueries[$id]);
345
                    unset($signs[$id]);
346
                }
347
            }
348
        }
349
350
351
        // Check, if there are no decomposed subqueries
352
        if (count($terms) == 0 ) {
353
            // return prepared candidate
354
            return $optimizedQuery;
355
        }
356
357
358
        // Check, if all subqueries have been decomposed and all terms has the same boost factor
359
        if (count($subqueries) == 0  &&  count(array_unique($boostFactors)) == 1) {
360
            require_once 'Zend/Search/Lucene/Search/Query/MultiTerm.php';
361
            $optimizedQuery = new Zend_Search_Lucene_Search_Query_MultiTerm($terms, $tsigns);
362
            $optimizedQuery->setBoost(reset($boostFactors)*$this->getBoost());
363
364
            return $optimizedQuery;
365
        }
366
367
368
        // This boolean query can't be transformed to Term/MultiTerm query and still contains
369
        // several subqueries
370
371
        // Separate prohibited terms
372
        $prohibitedTerms        = array();
373
        foreach ($terms as $id => $term) {
374
            if ($tsigns[$id] === false) {
375
                $prohibitedTerms[]        = $term;
376
377
                unset($terms[$id]);
378
                unset($tsigns[$id]);
379
                unset($boostFactors[$id]);
380
            }
381
        }
382
383
        if (count($terms) == 1) {
384
            require_once 'Zend/Search/Lucene/Search/Query/Term.php';
385
            $clause = new Zend_Search_Lucene_Search_Query_Term(reset($terms));
386
            $clause->setBoost(reset($boostFactors));
387
388
            $subqueries[] = $clause;
389
            $signs[]      = reset($tsigns);
390
391
            // Clear terms list
392
            $terms = array();
393
        } else if (count($terms) > 1  &&  count(array_unique($boostFactors)) == 1) {
394
            require_once 'Zend/Search/Lucene/Search/Query/MultiTerm.php';
395
            $clause = new Zend_Search_Lucene_Search_Query_MultiTerm($terms, $tsigns);
396
            $clause->setBoost(reset($boostFactors));
397
398
            $subqueries[] = $clause;
399
            // Clause sign is 'required' if clause contains required terms. 'Optional' otherwise.
400
            $signs[]      = (in_array(true, $tsigns))? true : null;
401
402
            // Clear terms list
403
            $terms = array();
404
        }
405
406
        if (count($prohibitedTerms) == 1) {
407
            // (boost factors are not significant for prohibited clauses)
408
            require_once 'Zend/Search/Lucene/Search/Query/Term.php';
409
            $subqueries[] = new Zend_Search_Lucene_Search_Query_Term(reset($prohibitedTerms));
410
            $signs[]      = false;
411
412
            // Clear prohibited terms list
413
            $prohibitedTerms = array();
414
        } else if (count($prohibitedTerms) > 1) {
415
            // prepare signs array
416
            $prohibitedSigns = array();
417
            foreach ($prohibitedTerms as $id => $term) {
418
                // all prohibited term are grouped as optional into multi-term query
419
                $prohibitedSigns[$id] = null;
420
            }
421
422
            // (boost factors are not significant for prohibited clauses)
423
            require_once 'Zend/Search/Lucene/Search/Query/MultiTerm.php';
424
            $subqueries[] = new Zend_Search_Lucene_Search_Query_MultiTerm($prohibitedTerms, $prohibitedSigns);
425
            // Clause sign is 'prohibited'
426
            $signs[]      = false;
427
428
            // Clear terms list
429
            $prohibitedTerms = array();
430
        }
431
432
        /** @todo Group terms with the same boost factors together */
433
434
        // Check, that all terms are processed
435
        // Replace candidate for optimized query
436
        if (count($terms) == 0  &&  count($prohibitedTerms) == 0) {
437
            $optimizedQuery = new Zend_Search_Lucene_Search_Query_Boolean($subqueries, $signs);
438
            $optimizedQuery->setBoost($this->getBoost());
439
        }
440
441
        return $optimizedQuery;
442
    }
443
444
    /**
445
     * Returns subqueries
446
     *
447
     * @return array
448
     */
449
    public function getSubqueries()
450
    {
451
        return $this->_subqueries;
452
    }
453
454
455
    /**
456
     * Return subqueries signs
457
     *
458
     * @return array
459
     */
460
    public function getSigns()
461
    {
462
        return $this->_signs;
463
    }
464
465
466
    /**
467
     * Constructs an appropriate Weight implementation for this query.
468
     *
469
     * @param Zend_Search_Lucene_Interface $reader
470
     * @return Zend_Search_Lucene_Search_Weight
471
     */
472
    public function createWeight(Zend_Search_Lucene_Interface $reader)
473
    {
474
        require_once 'Zend/Search/Lucene/Search/Weight/Boolean.php';
475
        $this->_weight = new Zend_Search_Lucene_Search_Weight_Boolean($this, $reader);
476
        return $this->_weight;
477
    }
478
479
480
    /**
481
     * Calculate result vector for Conjunction query
482
     * (like '<subquery1> AND <subquery2> AND <subquery3>')
483
     */
484
    private function _calculateConjunctionResult()
485
    {
486
        $this->_resVector = null;
487
488
        if (count($this->_subqueries) == 0) {
489
            $this->_resVector = array();
490
        }
491
492
        $resVectors      = array();
493
        $resVectorsSizes = array();
494
        $resVectorsIds   = array(); // is used to prevent arrays comparison
495
        foreach ($this->_subqueries as $subqueryId => $subquery) {
496
            $resVectors[]      = $subquery->matchedDocs();
497
            $resVectorsSizes[] = count(end($resVectors));
498
            $resVectorsIds[]   = $subqueryId;
499
        }
500
        // sort resvectors in order of subquery cardinality increasing
501
        array_multisort($resVectorsSizes, SORT_ASC, SORT_NUMERIC,
502
                        $resVectorsIds,   SORT_ASC, SORT_NUMERIC,
503
                        $resVectors);
504
505
        foreach ($resVectors as $nextResVector) {
506
            if($this->_resVector === null) {
507
                $this->_resVector = $nextResVector;
508
            } else {
509
                //$this->_resVector = array_intersect_key($this->_resVector, $nextResVector);
510
511
                /**
512
                 * This code is used as workaround for array_intersect_key() slowness problem.
513
                 */
514
                $updatedVector = array();
515
                foreach ($this->_resVector as $id => $value) {
516
                    if (isset($nextResVector[$id])) {
517
                        $updatedVector[$id] = $value;
518
                    }
519
                }
520
                $this->_resVector = $updatedVector;
521
            }
522
523
            if (count($this->_resVector) == 0) {
524
                // Empty result set, we don't need to check other terms
525
                break;
526
            }
527
        }
528
529
        // ksort($this->_resVector, SORT_NUMERIC);
530
        // Used algorithm doesn't change elements order
531
    }
532
533
534
    /**
535
     * Calculate result vector for non Conjunction query
536
     * (like '<subquery1> AND <subquery2> AND NOT <subquery3> OR <subquery4>')
537
     */
538
    private function _calculateNonConjunctionResult()
539
    {
540
        $requiredVectors      = array();
541
        $requiredVectorsSizes = array();
542
        $requiredVectorsIds   = array(); // is used to prevent arrays comparison
543
544
        $optional = array();
545
546
        foreach ($this->_subqueries as $subqueryId => $subquery) {
547
            if ($this->_signs[$subqueryId] === true) {
548
                // required
549
                $requiredVectors[]      = $subquery->matchedDocs();
550
                $requiredVectorsSizes[] = count(end($requiredVectors));
551
                $requiredVectorsIds[]   = $subqueryId;
552
            } elseif ($this->_signs[$subqueryId] === false) {
0 ignored issues
show
Unused Code introduced by
This elseif statement is empty, and could be removed.

This check looks for the bodies of elseif statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These elseif bodies can be removed. If you have an empty elseif but statements in the else branch, consider inverting the condition.

Loading history...
553
                // prohibited
554
                // Do nothing. matchedDocs() may include non-matching id's
555
                // Calculating prohibited vector may take significant time, but do not affect the result
556
                // Skipped.
557
            } else {
558
                // neither required, nor prohibited
559
                // array union
560
                $optional += $subquery->matchedDocs();
561
            }
562
        }
563
564
        // sort resvectors in order of subquery cardinality increasing
565
        array_multisort($requiredVectorsSizes, SORT_ASC, SORT_NUMERIC,
566
                        $requiredVectorsIds,   SORT_ASC, SORT_NUMERIC,
567
                        $requiredVectors);
568
569
        $required = null;
570
        foreach ($requiredVectors as $nextResVector) {
571
            if($required === null) {
572
                $required = $nextResVector;
573
            } else {
574
                //$required = array_intersect_key($required, $nextResVector);
575
576
                /**
577
                 * This code is used as workaround for array_intersect_key() slowness problem.
578
                 */
579
                $updatedVector = array();
580
                foreach ($required as $id => $value) {
581
                    if (isset($nextResVector[$id])) {
582
                        $updatedVector[$id] = $value;
583
                    }
584
                }
585
                $required = $updatedVector;
586
            }
587
588
            if (count($required) == 0) {
589
                // Empty result set, we don't need to check other terms
590
                break;
591
            }
592
        }
593
594
595
        if ($required !== null) {
596
            $this->_resVector = &$required;
597
        } else {
598
            $this->_resVector = &$optional;
599
        }
600
601
        ksort($this->_resVector, SORT_NUMERIC);
602
    }
603
604
605
    /**
606
     * Score calculator for conjunction queries (all subqueries are required)
607
     *
608
     * @param integer $docId
609
     * @param Zend_Search_Lucene_Interface $reader
610
     * @return float
611
     */
612
    public function _conjunctionScore($docId, Zend_Search_Lucene_Interface $reader)
613
    {
614
        if ($this->_coord === null) {
615
            $this->_coord = $reader->getSimilarity()->coord(count($this->_subqueries),
616
                                                            count($this->_subqueries) );
617
        }
618
619
        $score = 0;
620
621
        foreach ($this->_subqueries as $subquery) {
622
            $subscore = $subquery->score($docId, $reader);
623
624
            if ($subscore == 0) {
625
                return 0;
626
            }
627
628
            $score += $subquery->score($docId, $reader) * $this->_coord;
629
        }
630
631
        return $score * $this->_coord * $this->getBoost();
632
    }
633
634
635
    /**
636
     * Score calculator for non conjunction queries (not all subqueries are required)
637
     *
638
     * @param integer $docId
639
     * @param Zend_Search_Lucene_Interface $reader
640
     * @return float
641
     */
642
    public function _nonConjunctionScore($docId, Zend_Search_Lucene_Interface $reader)
643
    {
644
        if ($this->_coord === null) {
645
            $this->_coord = array();
646
647
            $maxCoord = 0;
648
            foreach ($this->_signs as $sign) {
649
                if ($sign !== false /* not prohibited */) {
650
                    $maxCoord++;
651
                }
652
            }
653
654
            for ($count = 0; $count <= $maxCoord; $count++) {
655
                $this->_coord[$count] = $reader->getSimilarity()->coord($count, $maxCoord);
656
            }
657
        }
658
659
        $score = 0;
660
        $matchedSubqueries = 0;
661
        foreach ($this->_subqueries as $subqueryId => $subquery) {
662
            $subscore = $subquery->score($docId, $reader);
663
664
            // Prohibited
665
            if ($this->_signs[$subqueryId] === false && $subscore != 0) {
666
                return 0;
667
            }
668
669
            // is required, but doen't match
670
            if ($this->_signs[$subqueryId] === true &&  $subscore == 0) {
671
                return 0;
672
            }
673
674
            if ($subscore != 0) {
675
                $matchedSubqueries++;
676
                $score += $subscore;
677
            }
678
        }
679
680
        return $score * $this->_coord[$matchedSubqueries] * $this->getBoost();
681
    }
682
683
    /**
684
     * Execute query in context of index reader
685
     * It also initializes necessary internal structures
686
     *
687
     * @param Zend_Search_Lucene_Interface $reader
688
     * @param Zend_Search_Lucene_Index_DocsFilter|null $docsFilter
689
     */
690
    public function execute(Zend_Search_Lucene_Interface $reader, $docsFilter = null)
691
    {
692
        // Initialize weight if it's not done yet
693
        $this->_initWeight($reader);
694
695
        if ($docsFilter === null) {
696
            // Create local documents filter if it's not provided by upper query
697
            require_once 'Zend/Search/Lucene/Index/DocsFilter.php';
698
            $docsFilter = new Zend_Search_Lucene_Index_DocsFilter();
699
        }
700
701
        foreach ($this->_subqueries as $subqueryId => $subquery) {
702
            if ($this->_signs == null  ||  $this->_signs[$subqueryId] === true) {
703
                // Subquery is required
704
                $subquery->execute($reader, $docsFilter);
705
            } else {
706
                $subquery->execute($reader);
707
            }
708
        }
709
710
        if ($this->_signs === null) {
711
            $this->_calculateConjunctionResult();
712
        } else {
713
            $this->_calculateNonConjunctionResult();
714
        }
715
    }
716
717
718
719
    /**
720
     * Get document ids likely matching the query
721
     *
722
     * It's an array with document ids as keys (performance considerations)
723
     *
724
     * @return array
725
     */
726
    public function matchedDocs()
727
    {
728
        return $this->_resVector;
729
    }
730
731
    /**
732
     * Score specified document
733
     *
734
     * @param integer $docId
735
     * @param Zend_Search_Lucene_Interface $reader
736
     * @return float
737
     */
738
    public function score($docId, Zend_Search_Lucene_Interface $reader)
739
    {
740
        if (isset($this->_resVector[$docId])) {
741
            if ($this->_signs === null) {
742
                return $this->_conjunctionScore($docId, $reader);
743
            } else {
744
                return $this->_nonConjunctionScore($docId, $reader);
745
            }
746
        } else {
747
            return 0;
748
        }
749
    }
750
751
    /**
752
     * Return query terms
753
     *
754
     * @return array
755
     */
756
    public function getQueryTerms()
757
    {
758
        $terms = array();
759
760
        foreach ($this->_subqueries as $id => $subquery) {
761
            if ($this->_signs === null  ||  $this->_signs[$id] !== false) {
762
                $terms = array_merge($terms, $subquery->getQueryTerms());
763
            }
764
        }
765
766
        return $terms;
767
    }
768
769
    /**
770
     * Query specific matches highlighting
771
     *
772
     * @param Zend_Search_Lucene_Search_Highlighter_Interface $highlighter  Highlighter object (also contains doc for highlighting)
773
     */
774
    protected function _highlightMatches(Zend_Search_Lucene_Search_Highlighter_Interface $highlighter)
775
    {
776
        foreach ($this->_subqueries as $id => $subquery) {
777
            if ($this->_signs === null  ||  $this->_signs[$id] !== false) {
778
                $subquery->_highlightMatches($highlighter);
779
            }
780
        }
781
    }
782
783
    /**
784
     * Print a query
785
     *
786
     * @return string
787
     */
788
    public function __toString()
789
    {
790
        // It's used only for query visualisation, so we don't care about characters escaping
791
792
        $query = '';
793
794
        foreach ($this->_subqueries as $id => $subquery) {
795
            if ($id != 0) {
796
                $query .= ' ';
797
            }
798
799
            if ($this->_signs === null || $this->_signs[$id] === true) {
800
                $query .= '+';
801
            } else if ($this->_signs[$id] === false) {
802
                $query .= '-';
803
            }
804
805
            $query .= '(' . $subquery->__toString() . ')';
806
        }
807
808
        if ($this->getBoost() != 1) {
809
            $query = '(' . $query . ')^' . round($this->getBoost(), 4);
810
        }
811
812
        return $query;
813
    }
814
}
815
816