/zf/library/Zend/Search/Lucene/Search/Query/Boolean.php
PHP | 815 lines | 440 code | 125 blank | 250 comment | 112 complexity | ced81a6ad5d68d0a9db69b83cd716afd MD5 | raw file
Possible License(s): MIT, BSD-3-Clause, Apache-2.0, LGPL-2.1, LGPL-3.0, BSD-2-Clause
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 license@zend.com 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-2011 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 23775 2011-03-01 17:25:24Z ralph $ 21 */ 22 23 24/** Zend_Search_Lucene_Search_Query */ 25require_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-2011 Zend Technologies USA Inc. (http://www.zend.com) 33 * @license http://framework.zend.com/license/new-bsd New BSD License 34 */ 35class 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 85 */ 86 public function __construct($subqueries = null, $signs = null) 87 { 88 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 } 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 public function addSubquery(Zend_Search_Lucene_Search_Query $subquery, $sign=null) { 118 if ($sign !== true || $this->_signs !== null) { // Skip, if all subqueries are required 119 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 $this->_signs[] = $sign; 126 } 127 128 $this->_subqueries[] = $subquery; 129 } 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 public function rewrite(Zend_Search_Lucene_Interface $index) 138 { 139 $query = new Zend_Search_Lucene_Search_Query_Boolean(); 140 $query->setBoost($this->getBoost()); 141 142 foreach ($this->_subqueries as $subqueryId => $subquery) { 143 $query->addSubquery($subquery->rewrite($index), 144 ($this->_signs === null)? true : $this->_signs[$subqueryId]); 145 } 146 147 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 public function optimize(Zend_Search_Lucene_Interface $index) 157 { 158 $subqueries = array(); 159 $signs = array(); 160 161 // Optimize all subqueries 162 foreach ($this->_subqueries as $id => $subquery) { 163 $subqueries[] = $subquery->optimize($index); 164 $signs[] = ($this->_signs === null)? true : $this->_signs[$id]; 165 } 166 167 // Remove insignificant subqueries 168 foreach ($subqueries as $id => $subquery) { 169 if ($subquery instanceof Zend_Search_Lucene_Search_Query_Insignificant) { 170 // Insignificant subquery has to be removed anyway 171 unset($subqueries[$id]); 172 unset($signs[$id]); 173 } 174 } 175 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 $allProhibited = true; 182 foreach ($signs as $sign) { 183 if ($sign !== false) { 184 $allProhibited = false; 185 break; 186 } 187 } 188 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 foreach ($subqueries as $id => $subquery) { 196 if ($subquery instanceof Zend_Search_Lucene_Search_Query_Empty) { 197 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 unset($subqueries[$id]); 205 unset($signs[$id]); 206 } 207 } 208 } 209 210 // Check, if reduced subqueries list is empty 211 if (count($subqueries) == 0) { 212 require_once 'Zend/Search/Lucene/Search/Query/Empty.php'; 213 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) { 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