PageRenderTime 40ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/util/sfLuceneCriteria.class.php

https://github.com/metaldave/sfLucenePlugin
PHP | 422 lines | 356 code | 18 blank | 48 comment | 6 complexity | a8ea93ac8666bc3f5712affc9ce9b591 MD5 | raw file
  1. <?php
  2. /*
  3. * This file is part of the sfLucenePlugin package
  4. * (c) 2007 - 2008 Carl Vondrick <carl@carlsoft.net>
  5. *
  6. * For the full copyright and license information, please view the LICENSE
  7. * file that was distributed with this source code.
  8. */
  9. /**
  10. * Provides a clean way to search the index, mimicking Propel Criteria.
  11. *
  12. * Usage example: <code>
  13. * $c = sfLuceneCriteria::newInstance()->add('the cool dude')->addField('sfl_category', array('Forum', 'Blog'));
  14. * </code>
  15. *
  16. * This class is not meant to reinvent the entire Zend Lucene's API, but rather
  17. * provide a simpler way to search. It is possible to combine queries built with
  18. * the Zend API too.
  19. *
  20. * @package sfLucenePlugin
  21. * @subpackage Utilities
  22. * @author Carl Vondrick <carl@carlsoft.net>
  23. * @version SVN: $Id: sfLuceneCriteria.class.php 15602 2009-02-18 15:12:02Z rande $
  24. */
  25. class sfLuceneCriteria
  26. {
  27. protected $query = null;
  28. protected $sorts = array();
  29. protected $scoring = null;
  30. protected $search = null;
  31. public function __construct(sfLucene $search)
  32. {
  33. sfLuceneToolkit::loadZend();
  34. $this->query = new Zend_Search_Lucene_Search_Query_Boolean();
  35. $this->search = $search;
  36. }
  37. /**
  38. * Simply provides a way to do one line method chaining
  39. *
  40. * @return sfLuceneCriteria
  41. */
  42. static public function newInstance(sfLucene $search)
  43. {
  44. return new self($search);
  45. }
  46. /**
  47. * Adds a subquery to the query itself. It accepts either a string which will
  48. * be parsed or a Zend query.
  49. *
  50. * @return sfLuceneCriteria
  51. */
  52. public function add($query, $type = true)
  53. {
  54. if (is_string($query))
  55. {
  56. $this->addString($query, null, $type);
  57. }
  58. else
  59. {
  60. if ($query instanceof self)
  61. {
  62. if ($query === $this)
  63. {
  64. throw new sfLuceneException('You cannot add an instance to itself');
  65. }
  66. $query = $query->getQuery();
  67. }
  68. if (!($query instanceof Zend_Search_Lucene_Search_Query))
  69. {
  70. throw new sfLuceneException('Invalid query given (must be instance of Zend_Search_Lucene_Search_Query)');
  71. }
  72. $this->query->addSubquery($query, $type);
  73. }
  74. return $this;
  75. }
  76. /**
  77. * Adds a string that is parsed into Zend API queries
  78. * @param string $query The query to parse
  79. * @param string $encoding The encoding to parse query as
  80. *
  81. * @return sfLuceneCriteria
  82. */
  83. public function addString($query, $encoding = null, $type = true)
  84. {
  85. $this->search->configure(); // setup query parser
  86. $this->add(Zend_Search_Lucene_Search_QueryParser::parse($query, $encoding), $type);
  87. return $this;
  88. }
  89. /**
  90. * This does a sane add on the current query. The query parser tends to throw a lot
  91. * of exceptions even in normal conditions, so we need to intercept them and then fall back
  92. * into a reduced state mode should the user have entered invalid syntax.
  93. *
  94. * @return sfLuceneCriteria
  95. */
  96. public function addSane($query, $type = true, $fatal = false)
  97. {
  98. try
  99. {
  100. return $this->add($query, $type);
  101. }
  102. catch (Zend_Search_Lucene_Search_QueryParserException $e)
  103. {
  104. if (!is_string($query))
  105. {
  106. if ($fatal)
  107. {
  108. throw $e;
  109. }
  110. else
  111. {
  112. return $this;
  113. }
  114. }
  115. try
  116. {
  117. $replacements = array('+', '-', '&', '|', '!', '(', ')', '{', '}', '[', ']', '^', '"', '~', '*', '?', ':', '\\', ' and ', ' or ', ' not ');
  118. $query = ' ' . $query . ' ';
  119. $query = str_replace($replacements, '', $query);
  120. $query = trim($query);
  121. return $this->add($query, $type);
  122. }
  123. catch (Zend_Search_Lucene_Search_QueryParserException $e)
  124. {
  125. if ($fatal)
  126. {
  127. throw $e;
  128. }
  129. else
  130. {
  131. return $this;
  132. }
  133. }
  134. }
  135. }
  136. /**
  137. * Adds a field to the search query.
  138. * @param mixed $values The values to search on
  139. * @param string $field The field to search under (null for all)
  140. * @param bool $matchAll If true, it will match all. False will match none. Null is neutral.
  141. * @param bool $type The type of subquery to add.
  142. *
  143. * @return sfLuceneCriteria
  144. */
  145. public function addField($values, $field = null, $matchAll = null, $type = true)
  146. {
  147. if (is_array($values))
  148. {
  149. $query = $this->getNewCriteria();
  150. foreach($values as $value)
  151. {
  152. $value = (string) $value;
  153. $term = new Zend_Search_Lucene_Index_Term($value, $field);
  154. $qterm = new Zend_Search_Lucene_Search_Query_Term($term);
  155. $query->add($qterm, $matchAll);
  156. }
  157. }
  158. elseif (is_scalar($values))
  159. {
  160. $values = (string) $values;
  161. $term = new Zend_Search_Lucene_Index_Term($values, $field);
  162. $query = new Zend_Search_Lucene_Search_Query_Term($term);
  163. }
  164. else
  165. {
  166. throw new sfLuceneException('Unknown field value type');
  167. }
  168. return $this->add($query, $type);
  169. }
  170. /**
  171. * Adds a multiterm query.
  172. *
  173. * @return sfLuceneCriteria
  174. */
  175. public function addMultiTerm($values, $field = null, $matchType = null, $type = true)
  176. {
  177. if (!is_array($values))
  178. {
  179. $values = array($values);
  180. }
  181. $query = new Zend_Search_Lucene_Search_Query_MultiTerm();
  182. foreach ($values as $value)
  183. {
  184. $query->addTerm(new Zend_Search_Lucene_Index_Term($value, $field), $matchType);
  185. }
  186. return $this->add($query, $type);
  187. }
  188. /**
  189. * Adds a wildcard field.
  190. * ? is used as single character wildcard
  191. * * is used as a multi character wildcard.
  192. *
  193. * @return sfLuceneCriteria
  194. */
  195. public function addWildcard($value, $field = null, $type = true)
  196. {
  197. $pattern = new Zend_Search_Lucene_Index_Term($value, $field);
  198. $query = new Zend_Search_Lucene_Search_Query_Wildcard($pattern);
  199. return $this->add($query, $type);
  200. }
  201. /**
  202. * Adds a phrase query
  203. *
  204. * @return sfLuceneCriteria
  205. */
  206. public function addPhrase($keywords, $field = null, $slop = 0, $type = true)
  207. {
  208. $query = new Zend_Search_Lucene_Search_Query_Phrase(array_values($keywords), array_keys($keywords), $field);
  209. $query->setSlop($slop);
  210. return $this->add($query, $type);
  211. }
  212. /**
  213. * Adds a range subquery
  214. *
  215. * @return sfLuceneCriteria
  216. */
  217. public function addRange($start = null, $stop = null, $field = null, $inclusive = true, $type = true)
  218. {
  219. if ($start)
  220. {
  221. $start = new Zend_Search_Lucene_Index_Term($start, $field);
  222. }
  223. else
  224. {
  225. $start = null;
  226. }
  227. if ($stop)
  228. {
  229. $stop = new Zend_Search_Lucene_Index_Term($stop, $field);
  230. }
  231. else
  232. {
  233. $stop = null;
  234. }
  235. if ($stop == null && $start == null)
  236. {
  237. throw new sfLuceneException('You must specify at least a start or stop in a range query.');
  238. }
  239. $query = new Zend_Search_Lucene_Search_Query_Range($start, $stop, $inclusive);
  240. return $this->add($query, $type);
  241. }
  242. /**
  243. * Adds a proximity query to restrict by distance from longitude and latitude.
  244. *
  245. * This method will do a pretty good calculation to restrict the results to
  246. * fall under a certain distance from an origin point.
  247. *
  248. * This method is not restricted to one particular unit, except you must be
  249. * consistent! This means you can use miles or kilometers (or centimeters)
  250. * and you can use degrees North or degrees South.
  251. *
  252. * The average radius of Earth is 3962 mi or 6378.1 km.
  253. *
  254. * @param float $latitude The origin latitude in degrees
  255. * @param float $longitude The origin longitude in degrees
  256. * @param int $proximity The maximun proximity in any unit.
  257. * @param int $radius The average radius of Earth in the same unit as $proximity
  258. * @param string $latitudeField The field to search under for latitudes.
  259. * @param string $longitudeField The field to search under for longitude.
  260. * @param mixed $type The type of restraint
  261. *
  262. * @return sfLuceneCriteria
  263. */
  264. public function addProximity($latitude, $longitude, $proximity, $radius = 6378.1, $latitudeField = 'latitude', $longitudeField = 'longitude', $type = true)
  265. {
  266. if ($radius <= 0)
  267. {
  268. throw new sfLuceneException('Radius must be greater than 0');
  269. }
  270. elseif ($proximity <= 0)
  271. {
  272. throw new sfLuceneException('Proximity must be greater than 0');
  273. }
  274. $perLatitude = M_PI * $radius / 180;
  275. $latitudeChange = $proximity / $perLatitude;
  276. $north = $latitude + $latitudeChange;
  277. $south = $latitude - $latitudeChange;
  278. $longitudeChange = $proximity / (cos(deg2rad($latitude)) * $perLatitude);
  279. $east = $longitude + $longitudeChange;
  280. $west = $longitude - $longitudeChange;
  281. $latitudeLower = min($north, $south);
  282. $latitudeUpper = max($north, $south);
  283. $longitudeLower = min($east, $west);
  284. $longitudeUpper = max($east, $west);
  285. $subquery = $this->getNewCriteria();
  286. $subquery->addRange($latitudeLower, $latitudeUpper, $latitudeField, true, true);
  287. $subquery->addRange($longitudeLower, $longitudeUpper, $longitudeField, true, true);
  288. return $this->add($subquery, $type);
  289. }
  290. /**
  291. *
  292. * @param string $field
  293. * @param interger $type
  294. *
  295. * @return sfLuceneCriteria
  296. */
  297. public function addAscendingSortBy($field, $type = SORT_REGULAR)
  298. {
  299. return $this->addSortBy($field, SORT_ASC, $type);
  300. }
  301. /**
  302. *
  303. * @param string $field
  304. * @param interger $type
  305. *
  306. * @return sfLuceneCriteria
  307. */
  308. public function addDescendingSortBy($field, $type = SORT_REGULAR)
  309. {
  310. return $this->addSortBy($field, SORT_DESC, $type);
  311. }
  312. /**
  313. *
  314. * @param string $field
  315. * @param interger $type
  316. *
  317. * @return sfLuceneCriteria
  318. */
  319. public function addSortBy($field, $order = SORT_ASC, $type = SORT_REGULAR)
  320. {
  321. $this->sorts[] = array('field' => $field, 'order' => $order, 'type' => $type);
  322. return $this;
  323. }
  324. /**
  325. * Sets the scoring algorithm for this query.
  326. * @param null|Zend_Search_Lucene_Search_Similarity $algorithm An instance of the algorithm to use (null for default)
  327. *
  328. * @return sfLuceneCriteria
  329. */
  330. public function setScoringAlgorithm($algorithm)
  331. {
  332. if ($algorithm != null && !($algorithm instanceof Zend_Search_Lucene_Search_Similarity))
  333. {
  334. throw new sfLuceneException('Scoring algorithm must either be null (for default) or an instance of Zend_Search_Lucene_Search_Similarity');
  335. }
  336. $this->scoring = $algorithm;
  337. return $this;
  338. }
  339. /**
  340. * Returns a Zend_Search_Lucene query that can be fed directly to Lucene
  341. */
  342. public function getQuery()
  343. {
  344. return $this->query;
  345. }
  346. public function getSorts()
  347. {
  348. return $this->sorts;
  349. }
  350. public function getScoringAlgorithm()
  351. {
  352. return $this->scoring;
  353. }
  354. /**
  355. * .
  356. *
  357. * @return sfLuceneCriteria
  358. */
  359. public function getNewCriteria()
  360. {
  361. return new self($this->search);
  362. }
  363. }