# Source for file Service.php

Documentation is available at Service.php

1. <?php
2. /**
3. * Copyright (c) 2007-2011, Servigistics, Inc.
4. * All rights reserved.
5. *
6. * Redistribution and use in source and binary forms, with or without
7. * modification, are permitted provided that the following conditions are met:
8. *
9. * - Redistributions of source code must retain the above copyright notice,
10. * this list of conditions and the following disclaimer.
11. * - Redistributions in binary form must reproduce the above copyright
12. * notice, this list of conditions and the following disclaimer in the
13. * documentation and/or other materials provided with the distribution.
14. * - Neither the name of Servigistics, Inc. nor the names of
15. * its contributors may be used to endorse or promote products derived from
16. * this software without specific prior written permission.
17. *
18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
22. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28. * POSSIBILITY OF SUCH DAMAGE.
29. *
30. * @copyright Copyright 2007-2011 Servigistics, Inc. (http://servigistics.com)
31. * @license http://solr-php-client.googlecode.com/svn/trunk/COPYING New BSD
32. * @version $Id: Service.php 59 2011-02-08 20:38:59Z donovan.jimenez $
33. *
34. * @package Apache
35. * @subpackage Solr
36. * @author Donovan Jimenez <djimenez@conduit-it.com>
37. */
39. // See Issue #1 (http://code.google.com/p/solr-php-client/issues/detail?id=1)
40. // Doesn't follow typical include path conventions, but is more convenient for users
41. require_once(dirname(__FILE__) . '/Exception.php');
42. require_once(dirname(__FILE__) . '/HttpTransportException.php');
43. require_once(dirname(__FILE__) . '/InvalidArgumentException.php');
45. require_once(dirname(__FILE__) . '/Document.php');
46. require_once(dirname(__FILE__) . '/Response.php');
48. require_once(dirname(__FILE__) . '/HttpTransport/Interface.php');
50. /**
51. * Starting point for the Solr API. Represents a Solr server resource and has
52. * methods for pinging, adding, deleting, committing, optimizing and searching.
53. *
54. * Example Usage:
55. * <code>
56. * ...
57. * $solr = new Apache_Solr_Service(); //or explicitly new Apache_Solr_Service('localhost', 8180, '/solr')
58. *
59. * if ($solr->ping())
60. * {
61. * $solr->deleteByQuery('*:*'); //deletes ALL documents - be careful :)
62. *
63. * $document = new Apache_Solr_Document();
64. * $document->id = uniqid(); //or something else suitably unique
65. *
66. * $document->title = 'Some Title';
67. * $document->content = 'Some content for this wonderful document. Blah blah blah.';
68. *
69. * $solr->addDocument($document); //if you're going to be adding documents in bulk using addDocuments
70. * //with an array of documents is faster
71. *
72. * $solr->commit(); //commit to see the deletes and the document
73. * $solr->optimize(); //merges multiple segments into one
74. *
75. * //and the one we all care about, search!
76. * //any other common or custom parameters to the request handler can go in the
77. * //optional 4th array argument.
78. * $solr->search('content:blah', 0, 10, array('sort' => 'timestamp desc'));
79. * }
80. * ...
81. * </code>
82. *
83. * @todo Investigate using other HTTP clients other than file_get_contents built-in handler. Could provide performance
84. * improvements when dealing with multiple requests by using HTTP's keep alive functionality
85. */
86. class Apache_Solr_Service
87. {
88. /**
89. * SVN Revision meta data for this class
90. */
91. const SVN_REVISION = '$Revision: 59 $';
93. /**
94. * SVN ID meta data for this class
95. */
96. const SVN_ID = '$Id: Service.php 59 2011-02-08 20:38:59Z donovan.jimenez $';
98. /**
99. * Response writer we'll request - JSON. See http://code.google.com/p/solr-php-client/issues/detail?id=6#c1 for reasoning
100. */
101. const SOLR_WRITER = 'json';
103. /**
104. * NamedList Treatment constants
105. */
106. const NAMED_LIST_FLAT = 'flat';
107. const NAMED_LIST_MAP = 'map';
109. /**
110. * Search HTTP Methods
111. */
112. const METHOD_GET = 'GET';
113. const METHOD_POST = 'POST';
115. /**
116. * Servlet mappings
117. */
118. const PING_SERVLET = 'admin/ping';
119. const UPDATE_SERVLET = 'update';
120. const SEARCH_SERVLET = 'select';
121. const THREADS_SERVLET = 'admin/threads';
122. const EXTRACT_SERVLET = 'update/extract';
124. /**
125. * Server identification strings
126. *
127. * @var string
128. */
129. protected $_host, $_port, $_path;
131. /**
132. * Whether {@link Apache_Solr_Response} objects should create {@link Apache_Solr_Document}s in
133. * the returned parsed data
134. *
135. * @var boolean
136. */
137. protected $_createDocuments = true;
139. /**
140. * Whether {@link Apache_Solr_Response} objects should have multivalue fields with only a single value
141. * collapsed to appear as a single value would.
142. *
143. * @var boolean
144. */
145. protected $_collapseSingleValueArrays = true;
147. /**
148. * How NamedLists should be formatted in the output. This specifically effects facet counts. Valid values
149. * are {@link Apache_Solr_Service::NAMED_LIST_MAP} (default) or {@link Apache_Solr_Service::NAMED_LIST_FLAT}.
150. *
151. * @var string
152. */
153. protected $_namedListTreatment = self::NAMED_LIST_MAP;
155. /**
156. * Query delimiters. Someone might want to be able to change
157. * these (to use &amp; instead of & for example), so I've provided them.
158. *
159. * @var string
160. */
161. protected $_queryDelimiter = '?', $_queryStringDelimiter = '&', $_queryBracketsEscaped = true;
163. /**
164. * Constructed servlet full path URLs
165. *
166. * @var string
167. */
168. protected $_pingUrl, $_updateUrl, $_searchUrl, $_threadsUrl;
170. /**
171. * Keep track of whether our URLs have been constructed
172. *
173. * @var boolean
174. */
175. protected $_urlsInited = false;
177. /**
178. * HTTP Transport implementation (pluggable)
179. *
180. * @var Apache_Solr_HttpTransport_Interface
181. */
182. protected $_httpTransport = false;
184. /**
185. * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
186. *
187. * NOTE: inside a phrase fewer characters need escaped, use {@link Apache_Solr_Service::escapePhrase()} instead
188. *
189. * @param string $value
190. * @return string
191. */
192. static public function escape($value)
193. {
194. //list taken from http://lucene.apache.org/java/docs/queryparsersyntax.html#Escaping%20Special%20Characters
195. $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/';
196. $replace = '\\\$1';
198. return preg_replace($pattern, $replace, $value);
199. }
201. /**
202. * Escape a value meant to be contained in a phrase for special query characters
203. *
204. * @param string $value
205. * @return string
206. */
207. static public function escapePhrase($value)
208. {
209. $pattern = '/("|\\\)/';
210. $replace = '\\\$1';
212. return preg_replace($pattern, $replace, $value);
213. }
215. /**
216. * Convenience function for creating phrase syntax from a value
217. *
218. * @param string $value
219. * @return string
220. */
221. static public function phrase($value)
222. {
223. return '"' . self::escapePhrase($value) . '"';
224. }
226. /**
227. * Constructor. All parameters are optional and will take on default values
228. * if not specified.
229. *
230. * @param string $host
231. * @param string $port
232. * @param string $path
233. * @param Apache_Solr_HttpTransport_Interface $httpTransport
234. */
235. public function __construct($host = 'localhost', $port = 8180, $path = '/solr/', $httpTransport = false)
236. {
237. $this->setHost($host);
238. $this->setPort($port);
239. $this->setPath($path);
241. $this->_initUrls();
243. if ($httpTransport)
244. {
245. $this->setHttpTransport($httpTransport);
246. }
248. // check that our php version is >= 5.1.3 so we can correct for http_build_query behavior later
249. $this->_queryBracketsEscaped = version_compare(phpversion(), '5.1.3', '>=');
250. }
252. /**
253. * Return a valid http URL given this server's host, port and path and a provided servlet name
254. *
255. * @param string $servlet
256. * @return string
257. */
258. protected function _constructUrl($servlet, $params = array())
259. {
260. if (count($params))
261. {
262. //escape all parameters appropriately for inclusion in the query string
263. $escapedParams = array();
265. foreach ($params as $key => $value)
266. {
267. $escapedParams[] = urlencode($key) . '=' . urlencode($value);
268. }
270. $queryString = $this->_queryDelimiter . implode($this->_queryStringDelimiter, $escapedParams);
271. }
272. else
273. {
274. $queryString = '';
275. }
277. return 'http://' . $this->_host . ':' . $this->_port . $this->_path . $servlet . $queryString;
278. }
280. /**
281. * Construct the Full URLs for the three servlets we reference
282. */
283. protected function _initUrls()
284. {
285. //Initialize our full servlet URLs now that we have server information
286. $this->_extractUrl = $this->_constructUrl(self::EXTRACT_SERVLET);
287. $this->_pingUrl = $this->_constructUrl(self::PING_SERVLET);
288. $this->_searchUrl = $this->_constructUrl(self::SEARCH_SERVLET);
289. $this->_threadsUrl = $this->_constructUrl(self::THREADS_SERVLET, array('wt' => self::SOLR_WRITER ));
290. $this->_updateUrl = $this->_constructUrl(self::UPDATE_SERVLET, array('wt' => self::SOLR_WRITER ));
292. $this->_urlsInited = true;
293. }
295. protected function _generateQueryString($params)
296. {
297. // use http_build_query to encode our arguments because its faster
298. // than urlencoding all the parts ourselves in a loop
299. //
300. // because http_build_query treats arrays differently than we want to, correct the query
301. // string by changing foo[#]=bar (# being an actual number) parameter strings to just
302. // multiple foo=bar strings. This regex should always work since '=' will be urlencoded
303. // anywhere else the regex isn't expecting it
304. //
305. // NOTE: before php 5.1.3 brackets were not url encoded by http_build query - we've checked
306. // the php version in the constructor and put the results in the instance variable. Also, before
307. // 5.1.2 the arg_separator parameter was not available, so don't use it
308. if ($this->_queryBracketsEscaped)
309. {
310. $queryString = http_build_query($params, null, $this->_queryStringDelimiter);
311. return preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', $queryString);
312. }
313. else
314. {
315. $queryString = http_build_query($params);
316. return preg_replace('/\\[(?:[0-9]|[1-9][0-9]+)\\]=/', '=', $queryString);
317. }
318. }
320. /**
321. * Central method for making a get operation against this Solr Server
322. *
323. * @param string $url
324. * @param float $timeout Read timeout in seconds
325. * @return Apache_Solr_Response
326. *
327. * @throws Apache_Solr_HttpTransportException If a non 200 response status is returned
328. */
329. protected function _sendRawGet($url, $timeout = FALSE)
330. {
331. $httpTransport = $this->getHttpTransport();
333. $httpResponse = $httpTransport->performGetRequest($url, $timeout);
334. $solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
336. if ($solrResponse->getHttpStatus() != 200)
337. {
338. throw new Apache_Solr_HttpTransportException($solrResponse);
339. }
341. return $solrResponse;
342. }
344. /**
345. * Central method for making a post operation against this Solr Server
346. *
347. * @param string $url
348. * @param string $rawPost
349. * @param float $timeout Read timeout in seconds
350. * @param string $contentType
351. * @return Apache_Solr_Response
352. *
353. * @throws Apache_Solr_HttpTransportException If a non 200 response status is returned
354. */
355. protected function _sendRawPost($url, $rawPost, $timeout = FALSE, $contentType = 'text/xml; charset=UTF-8')
356. {
357. $httpTransport = $this->getHttpTransport();
359. $httpResponse = $httpTransport->performPostRequest($url, $rawPost, $contentType, $timeout);
360. $solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
362. if ($solrResponse->getHttpStatus() != 200)
363. {
364. throw new Apache_Solr_HttpTransportException($solrResponse);
365. }
367. return $solrResponse;
368. }
370. /**
371. * Returns the set host
372. *
373. * @return string
374. */
375. public function getHost()
376. {
377. return $this->_host;
378. }
380. /**
381. * Set the host used. If empty will fallback to constants
382. *
383. * @param string $host
384. *
385. * @throws Apache_Solr_InvalidArgumentException If the host parameter is empty
386. */
387. public function setHost($host)
388. {
389. //Use the provided host or use the default
390. if (empty($host))
391. {
392. throw new Apache_Solr_InvalidArgumentException('Host parameter is empty');
393. }
394. else
395. {
396. $this->_host = $host;
397. }
399. if ($this->_urlsInited)
400. {
401. $this->_initUrls();
402. }
403. }
405. /**
406. * Get the set port
407. *
408. * @return integer
409. */
410. public function getPort()
411. {
412. return $this->_port;
413. }
415. /**
416. * Set the port used. If empty will fallback to constants
417. *
418. * @param integer $port
419. *
420. * @throws Apache_Solr_InvalidArgumentException If the port parameter is empty
421. */
422. public function setPort($port)
423. {
424. //Use the provided port or use the default
425. $port = (int) $port;
427. if ($port <= 0)
428. {
429. throw new Apache_Solr_InvalidArgumentException('Port is not a valid port number');
430. }
431. else
432. {
433. $this->_port = $port;
434. }
436. if ($this->_urlsInited)
437. {
438. $this->_initUrls();
439. }
440. }
442. /**
443. * Get the set path.
444. *
445. * @return string
446. */
447. public function getPath()
448. {
449. return $this->_path;
450. }
452. /**
453. * Set the path used. If empty will fallback to constants
454. *
455. * @param string $path
456. */
457. public function setPath($path)
458. {
459. $path = trim($path, '/');
461. $this->_path = '/' . $path . '/';
463. if ($this->_urlsInited)
464. {
465. $this->_initUrls();
466. }
467. }
469. /**
470. * Get the current configured HTTP Transport
471. *
472. * @return HttpTransportInterface
473. */
474. public function getHttpTransport()
475. {
476. // lazy load a default if one has not be set
477. if ($this->_httpTransport === false)
478. {
479. require_once(dirname(__FILE__) . '/HttpTransport/FileGetContents.php');
481. $this->_httpTransport = new Apache_Solr_HttpTransport_FileGetContents();
482. }
484. return $this->_httpTransport;
485. }
487. /**
488. * Set the HTTP Transport implemenation that will be used for all HTTP requests
489. *
490. * @param Apache_Solr_HttpTransport_Interface
491. */
492. public function setHttpTransport(Apache_Solr_HttpTransport_Interface $httpTransport)
493. {
494. $this->_httpTransport = $httpTransport;
495. }
497. /**
498. * Set the create documents flag. This determines whether {@link Apache_Solr_Response} objects will
499. * parse the response and create {@link Apache_Solr_Document} instances in place.
500. *
501. * @param boolean $createDocuments
502. */
503. public function setCreateDocuments($createDocuments)
504. {
505. $this->_createDocuments = (bool) $createDocuments;
506. }
508. /**
509. * Get the current state of teh create documents flag.
510. *
511. * @return boolean
512. */
513. public function getCreateDocuments()
514. {
515. return $this->_createDocuments;
516. }
518. /**
519. * Set the collapse single value arrays flag.
520. *
521. * @param boolean $collapseSingleValueArrays
522. */
523. public function setCollapseSingleValueArrays($collapseSingleValueArrays)
524. {
525. $this->_collapseSingleValueArrays = (bool) $collapseSingleValueArrays;
526. }
528. /**
529. * Get the current state of the collapse single value arrays flag.
530. *
531. * @return boolean
532. */
533. public function getCollapseSingleValueArrays()
534. {
535. return $this->_collapseSingleValueArrays;
536. }
538. /**
539. * Get the current default timeout setting (initially the default_socket_timeout ini setting)
540. * in seconds
541. *
542. * @return float
543. *
544. * @deprecated Use the getDefaultTimeout method on the HTTP transport implementation
545. */
546. public function getDefaultTimeout()
547. {
548. return $this->getHttpTransport()->getDefaultTimeout();
549. }
551. /**
552. * Set the default timeout for all calls that aren't passed a specific timeout
553. *
554. * @param float $timeout Timeout value in seconds
555. *
556. * @deprecated Use the setDefaultTimeout method on the HTTP transport implementation
557. */
558. public function setDefaultTimeout($timeout)
559. {
560. $this->getHttpTransport()->setDefaultTimeout($timeout);
561. }
563. /**
564. * Set how NamedLists should be formatted in the response data. This mainly effects
565. * the facet counts format.
566. *
567. * @param string $namedListTreatment
568. * @throws Apache_Solr_InvalidArgumentException If invalid option is set
569. */
570. public function setNamedListTreatment($namedListTreatment)
571. {
572. switch ((string) $namedListTreatment)
573. {
574. case Apache_Solr_Service::NAMED_LIST_FLAT:
575. $this->_namedListTreatment = Apache_Solr_Service::NAMED_LIST_FLAT;
576. break;
578. case Apache_Solr_Service::NAMED_LIST_MAP:
579. $this->_namedListTreatment = Apache_Solr_Service::NAMED_LIST_MAP;
580. break;
582. default:
583. throw new Apache_Solr_InvalidArgumentException('Not a valid named list treatement option');
584. }
585. }
587. /**
588. * Get the current setting for named list treatment.
589. *
590. * @return string
591. */
592. public function getNamedListTreatment()
593. {
594. return $this->_namedListTreatment;
595. }
597. /**
598. * Set the string used to separate the path form the query string.
599. * Defaulted to '?'
600. *
601. * @param string $queryDelimiter
602. */
603. public function setQueryDelimiter($queryDelimiter)
604. {
605. $this->_queryDelimiter = $queryDelimiter;
606. }
608. /**
609. * Set the string used to separate the parameters in thequery string
610. * Defaulted to '&'
611. *
612. * @param string $queryStringDelimiter
613. */
614. public function setQueryStringDelimiter($queryStringDelimiter)
615. {
616. $this->_queryStringDelimiter = $queryStringDelimiter;
617. }
619. /**
620. * Call the /admin/ping servlet, can be used to quickly tell if a connection to the
621. * server is able to be made.
622. *
623. * @param float $timeout maximum time to wait for ping in seconds, -1 for unlimited (default is 2)
624. * @return float Actual time taken to ping the server, FALSE if timeout or HTTP error status occurs
625. */
626. public function ping($timeout = 2)
627. {
628. $start = microtime(true);
630. $httpTransport = $this->getHttpTransport();
632. $httpResponse = $httpTransport->performHeadRequest($this->_pingUrl, $timeout);
633. $solrResponse = new Apache_Solr_Response($httpResponse, $this->_createDocuments, $this->_collapseSingleValueArrays);
635. if ($solrResponse->getHttpStatus() == 200)
636. {
637. return microtime(true) - $start;
638. }
639. else
640. {
641. return false;
642. }
643. }
645. /**
646. * Call the /admin/threads servlet and retrieve information about all threads in the
647. * Solr servlet's thread group. Useful for diagnostics.
648. *
649. * @return Apache_Solr_Response
650. *
651. * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
652. */
653. public function threads()
654. {
655. return $this->_sendRawGet($this->_threadsUrl);
656. }
658. /**
659. * Raw Add Method. Takes a raw post body and sends it to the update service. Post body
660. * should be a complete and well formed "add" xml document.
661. *
662. * @param string $rawPost
663. * @return Apache_Solr_Response
664. *
665. * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
666. */
667. public function add($rawPost)
668. {
669. return $this->_sendRawPost($this->_updateUrl, $rawPost);
670. }
672. /**
673. * Add a Solr Document to the index
674. *
675. * @param Apache_Solr_Document $document
676. * @param boolean $allowDups
677. * @param boolean $overwritePending
678. * @param boolean $overwriteCommitted
679. * @param integer $commitWithin The number of milliseconds that a document must be committed within, see @{link http://wiki.apache.org/solr/UpdateXmlMessages#The_Update_Schema} for details. If left empty this property will not be set in the request.
680. * @return Apache_Solr_Response
681. *
682. * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
683. */
684. public function addDocument(Apache_Solr_Document $document, $allowDups = false, $overwritePending = true, $overwriteCommitted = true, $commitWithin = 0)
685. {
686. $dupValue = $allowDups ? 'true' : 'false';
687. $pendingValue = $overwritePending ? 'true' : 'false';
688. $committedValue = $overwriteCommitted ? 'true' : 'false';
690. $commitWithin = (int) $commitWithin;
691. $commitWithinString = $commitWithin > 0 ? " commitWithin=\"{$commitWithin}\"" : '';
693. $rawPost = "<add allowDups=\"{$dupValue}\" overwritePending=\"{$pendingValue}\" overwriteCommitted=\"{$committedValue}\"{$commitWithinString}>";
694. $rawPost .= $this->_documentToXmlFragment($document);
695. $rawPost .= '</add>';
697. return $this->add($rawPost);
698. }
700. /**
701. * Add an array of Solr Documents to the index all at once
702. *
703. * @param array $documents Should be an array of Apache_Solr_Document instances
704. * @param boolean $allowDups
705. * @param boolean $overwritePending
706. * @param boolean $overwriteCommitted
707. * @param integer $commitWithin The number of milliseconds that a document must be committed within, see @{link http://wiki.apache.org/solr/UpdateXmlMessages#The_Update_Schema} for details. If left empty this property will not be set in the request.
708. * @return Apache_Solr_Response
709. *
710. * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
711. */
712. public function addDocuments($documents, $allowDups = false, $overwritePending = true, $overwriteCommitted = true, $commitWithin = 0)
713. {
714. $dupValue = $allowDups ? 'true' : 'false';
715. $pendingValue = $overwritePending ? 'true' : 'false';
716. $committedValue = $overwriteCommitted ? 'true' : 'false';
718. $commitWithin = (int) $commitWithin;
719. $commitWithinString = $commitWithin > 0 ? " commitWithin=\"{$commitWithin}\"" : '';
721. $rawPost = "<add allowDups=\"{$dupValue}\" overwritePending=\"{$pendingValue}\" overwriteCommitted=\"{$committedValue}\"{$commitWithinString}>";
723. foreach ($documents as $document)
724. {
725. if ($document instanceof Apache_Solr_Document)
726. {
727. $rawPost .= $this->_documentToXmlFragment($document);
728. }
729. }
731. $rawPost .= '</add>';
733. return $this->add($rawPost);
734. }
736. /**
737. * Create an XML fragment from a {@link Apache_Solr_Document} instance appropriate for use inside a Solr add call
738. *
739. * @return string
740. */
741. protected function _documentToXmlFragment(Apache_Solr_Document $document)
742. {
743. $xml = '<doc';
745. if ($document->getBoost() !== false)
746. {
747. $xml .= ' boost="' . $document->getBoost() . '"';
748. }
750. $xml .= '>';
752. foreach ($document as $key => $value)
753. {
754. $key = htmlspecialchars($key, ENT_QUOTES, 'UTF-8');
755. $fieldBoost = $document->getFieldBoost($key);
757. if (is_array($value))
758. {
759. foreach ($value as $multivalue)
760. {
761. $xml .= '<field name="' . $key . '"';
763. if ($fieldBoost !== false)
764. {
765. $xml .= ' boost="' . $fieldBoost . '"';
767. // only set the boost for the first field in the set
768. $fieldBoost = false;
769. }
771. $multivalue = htmlspecialchars($multivalue, ENT_NOQUOTES, 'UTF-8');
773. $xml .= '>' . $multivalue . '</field>';
774. }
775. }
776. else
777. {
778. $xml .= '<field name="' . $key . '"';
780. if ($fieldBoost !== false)
781. {
782. $xml .= ' boost="' . $fieldBoost . '"';
783. }
785. $value = htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8');
787. $xml .= '>' . $value . '</field>';
788. }
789. }
791. $xml .= '</doc>';
793. // replace any control characters to avoid Solr XML parser exception
794. return $this->_stripCtrlChars($xml);
795. }
797. /**
798. * Replace control (non-printable) characters from string that are invalid to Solr's XML parser with a space.
799. *
800. * @param string $string
801. * @return string
802. */
803. protected function _stripCtrlChars($string)
804. {
805. // See: http://w3.org/International/questions/qa-forms-utf-8.html
806. // Printable utf-8 does not include any of these chars below x7F
807. return preg_replace('@[\x00-\x08\x0B\x0C\x0E-\x1F]@', ' ', $string);
808. }
810. /**
811. * Send a commit command. Will be synchronous unless both wait parameters are set to false.
812. *
813. * @param boolean $expungeDeletes Defaults to false, merge segments with deletes away
814. * @param boolean $waitFlush Defaults to true, block until index changes are flushed to disk
815. * @param boolean $waitSearcher Defaults to true, block until a new searcher is opened and registered as the main query searcher, making the changes visible
816. * @param float $timeout Maximum expected duration (in seconds) of the commit operation on the server (otherwise, will throw a communication exception). Defaults to 1 hour
817. * @return Apache_Solr_Response
818. *
819. * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
820. */
821. public function commit($expungeDeletes = false, $waitFlush = true, $waitSearcher = true, $timeout = 3600)
822. {
823. $expungeValue = $expungeDeletes ? 'true' : 'false';
824. $flushValue = $waitFlush ? 'true' : 'false';
825. $searcherValue = $waitSearcher ? 'true' : 'false';
827. $rawPost = '<commit expungeDeletes="' . $expungeValue . '" waitFlush="' . $flushValue . '" waitSearcher="' . $searcherValue . '" />';
829. return $this->_sendRawPost($this->_updateUrl, $rawPost, $timeout);
830. }
832. /**
833. * Raw Delete Method. Takes a raw post body and sends it to the update service. Body should be
834. * a complete and well formed "delete" xml document
835. *
836. * @param string $rawPost Expected to be utf-8 encoded xml document
837. * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
838. * @return Apache_Solr_Response
839. *
840. * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
841. */
842. public function delete($rawPost, $timeout = 3600)
843. {
844. return $this->_sendRawPost($this->_updateUrl, $rawPost, $timeout);
845. }
847. /**
848. * Create a delete document based on document ID
849. *
850. * @param string $id Expected to be utf-8 encoded
851. * @param boolean $fromPending
852. * @param boolean $fromCommitted
853. * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
854. * @return Apache_Solr_Response
855. *
856. * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
857. */
858. public function deleteById($id, $fromPending = true, $fromCommitted = true, $timeout = 3600)
859. {
860. $pendingValue = $fromPending ? 'true' : 'false';
861. $committedValue = $fromCommitted ? 'true' : 'false';
863. //escape special xml characters
864. $id = htmlspecialchars($id, ENT_NOQUOTES, 'UTF-8');
866. $rawPost = '<delete fromPending="' . $pendingValue . '" fromCommitted="' . $committedValue . '"><id>' . $id . '</id></delete>';
868. return $this->delete($rawPost, $timeout);
869. }
871. /**
872. * Create and post a delete document based on multiple document IDs.
873. *
874. * @param array $ids Expected to be utf-8 encoded strings
875. * @param boolean $fromPending
876. * @param boolean $fromCommitted
877. * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
878. * @return Apache_Solr_Response
879. *
880. * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
881. */
882. public function deleteByMultipleIds($ids, $fromPending = true, $fromCommitted = true, $timeout = 3600)
883. {
884. $pendingValue = $fromPending ? 'true' : 'false';
885. $committedValue = $fromCommitted ? 'true' : 'false';
887. $rawPost = '<delete fromPending="' . $pendingValue . '" fromCommitted="' . $committedValue . '">';
889. foreach ($ids as $id)
890. {
891. //escape special xml characters
892. $id = htmlspecialchars($id, ENT_NOQUOTES, 'UTF-8');
894. $rawPost .= '<id>' . $id . '</id>';
895. }
897. $rawPost .= '</delete>';
899. return $this->delete($rawPost, $timeout);
900. }
902. /**
903. * Create a delete document based on a query and submit it
904. *
905. * @param string $rawQuery Expected to be utf-8 encoded
906. * @param boolean $fromPending
907. * @param boolean $fromCommitted
908. * @param float $timeout Maximum expected duration of the delete operation on the server (otherwise, will throw a communication exception)
909. * @return Apache_Solr_Response
910. *
911. * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
912. */
913. public function deleteByQuery($rawQuery, $fromPending = true, $fromCommitted = true, $timeout = 3600)
914. {
915. $pendingValue = $fromPending ? 'true' : 'false';
916. $committedValue = $fromCommitted ? 'true' : 'false';
918. // escape special xml characters
919. $rawQuery = htmlspecialchars($rawQuery, ENT_NOQUOTES, 'UTF-8');
921. $rawPost = '<delete fromPending="' . $pendingValue . '" fromCommitted="' . $committedValue . '"><query>' . $rawQuery . '</query></delete>';
923. return $this->delete($rawPost, $timeout);
924. }
926. /**
927. * Use Solr Cell to extract document contents. See {@link http://wiki.apache.org/solr/ExtractingRequestHandler} for information on how
928. * to use Solr Cell and what parameters are available.
929. *
930. * NOTE: when passing an Apache_Solr_Document instance, field names and boosts will automatically be prepended by "literal." and "boost."
931. * as appropriate. Any keys from the $params array will NOT be treated this way. Any mappings from the document will overwrite key / value
932. * pairs in the params array if they have the same name (e.g. you pass a "literal.id" key and value in your $params array but you also
933. * pass in a document isntance with an "id" field" - the document's value(s) will take precedence).
934. *
935. * @param string $file Path to file to extract data from
936. * @param array $params optional array of key value pairs that will be sent with the post (see Solr Cell documentation)
937. * @param Apache_Solr_Document $document optional document that will be used to generate post parameters (literal.* and boost.* params)
938. * @param string $mimetype optional mimetype specification (for the file being extracted)
939. *
940. * @return Apache_Solr_Response
941. *
942. * @throws Apache_Solr_InvalidArgumentException if $file, $params, or $document are invalid.
943. */
944. public function extract($file, $params = array(), $document = null, $mimetype = 'application/octet-stream')
945. {
946. // check if $params is an array (allow null for default empty array)
947. if (!is_null($params))
948. {
949. if (!is_array($params))
950. {
951. throw new Apache_Solr_InvalidArgumentException("\$params must be a valid array or null");
952. }
953. }
954. else
955. {
956. $params = array();
957. }
959. // if $file is an http request, defer to extractFromUrl instead
960. if (substr($file, 0, 7) == 'http://' || substr($file, 0, 8) == 'https://')
961. {
962. return $this->extractFromUrl($file, $params, $document, $mimetype);
963. }
965. // read the contents of the file
966. $contents = @file_get_contents($file);
968. if ($contents !== false)
969. {
970. // add the resource.name parameter if not specified
971. if (!isset($params['resource.name']))
972. {
973. $params['resource.name'] = basename($file);
974. }
976. // delegate the rest to extractFromString
977. return $this->extractFromString($contents, $params, $document, $mimetype);
978. }
979. else
980. {
981. throw new Apache_Solr_InvalidArgumentException("File '{$file}' is empty or could not be read");
982. }
983. }
985. /**
986. * Use Solr Cell to extract document contents. See {@link http://wiki.apache.org/solr/ExtractingRequestHandler} for information on how
987. * to use Solr Cell and what parameters are available.
988. *
989. * NOTE: when passing an Apache_Solr_Document instance, field names and boosts will automatically be prepended by "literal." and "boost."
990. * as appropriate. Any keys from the $params array will NOT be treated this way. Any mappings from the document will overwrite key / value
991. * pairs in the params array if they have the same name (e.g. you pass a "literal.id" key and value in your $params array but you also
992. * pass in a document isntance with an "id" field" - the document's value(s) will take precedence).
993. *
994. * @param string $data Data that will be passed to Solr Cell
995. * @param array $params optional array of key value pairs that will be sent with the post (see Solr Cell documentation)
996. * @param Apache_Solr_Document $document optional document that will be used to generate post parameters (literal.* and boost.* params)
997. * @param string $mimetype optional mimetype specification (for the file being extracted)
998. *
999. * @return Apache_Solr_Response
1000. *
1001. * @throws Apache_Solr_InvalidArgumentException if $file, $params, or $document are invalid.
1002. *
1003. * @todo Should be using multipart/form-data to post parameter values, but I could not get my implementation to work. Needs revisisted.
1004. */
1005. public function extractFromString($data, $params = array(), $document = null, $mimetype = 'application/octet-stream')
1006. {
1007. // check if $params is an array (allow null for default empty array)
1008. if (!is_null($params))
1009. {
1010. if (!is_array($params))
1011. {
1012. throw new Apache_Solr_InvalidArgumentException("\$params must be a valid array or null");
1013. }
1014. }
1015. else
1016. {
1017. $params = array();
1018. }
1020. // make sure we receive our response in JSON and have proper name list treatment
1021. $params['wt'] = self::SOLR_WRITER;
1022. $params['json.nl'] = $this->_namedListTreatment;
1024. // check if $document is an Apache_Solr_Document instance
1025. if (!is_null($document) && $document instanceof Apache_Solr_Document)
1026. {
1027. // iterate document, adding literal.* and boost.* fields to $params as appropriate
1028. foreach ($document as $field => $fieldValue)
1029. {
1030. // check if we need to add a boost.* parameters
1031. $fieldBoost = $document->getFieldBoost($field);
1033. if ($fieldBoost !== false)
1034. {
1035. $params["boost.{$field}"] = $fieldBoost;
1036. }
1038. // add the literal.* parameter
1039. $params["literal.{$field}"] = $fieldValue;
1040. }
1041. }
1043. // params will be sent to SOLR in the QUERY STRING
1044. $queryString = $this->_generateQueryString($params);
1046. // the file contents will be sent to SOLR as the POST BODY - we use application/octect-stream as default mimetype
1047. return $this->_sendRawPost($this->_extractUrl . $this->_queryDelimiter . $queryString, $data, false, $mimetype);
1048. }
1050. /**
1051. * Use Solr Cell to extract document contents. See {@link http://wiki.apache.org/solr/ExtractingRequestHandler} for information on how
1052. * to use Solr Cell and what parameters are available.
1053. *
1054. * NOTE: when passing an Apache_Solr_Document instance, field names and boosts will automatically be prepended by "literal." and "boost."
1055. * as appropriate. Any keys from the $params array will NOT be treated this way. Any mappings from the document will overwrite key / value
1056. * pairs in the params array if they have the same name (e.g. you pass a "literal.id" key and value in your $params array but you also
1057. * pass in a document isntance with an "id" field" - the document's value(s) will take precedence).
1058. *
1059. * @param string $url URL
1060. * @param array $params optional array of key value pairs that will be sent with the post (see Solr Cell documentation)
1061. * @param Apache_Solr_Document $document optional document that will be used to generate post parameters (literal.* and boost.* params)
1062. * @param string $mimetype optional mimetype specification (for the file being extracted)
1063. *
1064. * @return Apache_Solr_Response
1065. *
1066. * @throws Apache_Solr_InvalidArgumentException if $url, $params, or $document are invalid.
1067. */
1068. public function extractFromUrl($url, $params = array(), $document = null, $mimetype = 'application/octet-stream')
1069. {
1070. // check if $params is an array (allow null for default empty array)
1071. if (!is_null($params))
1072. {
1073. if (!is_array($params))
1074. {
1075. throw new Apache_Solr_InvalidArgumentException("\$params must be a valid array or null");
1076. }
1077. }
1078. else
1079. {
1080. $params = array();
1081. }
1083. $httpTransport = $this->getHttpTransport();
1085. // read the contents of the URL using our configured Http Transport and default timeout
1086. $httpResponse = $httpTransport->performGetRequest($url);
1088. // check that its a 200 response
1089. if ($httpResponse->getStatusCode() == 200)
1090. {
1091. // add the resource.name parameter if not specified
1092. if (!isset($params['resource.name']))
1093. {
1094. $params['resource.name'] = $url;
1095. }
1097. // delegate the rest to extractFromString
1098. return $this->extractFromString($httpResponse->getBody(), $params, $document, $mimetype);
1099. }
1100. else
1101. {
1102. throw new Apache_Solr_InvalidArgumentException("URL '{$url}' returned non 200 response code");
1103. }
1104. }
1106. /**
1107. * Send an optimize command. Will be synchronous unless both wait parameters are set
1108. * to false.
1109. *
1110. * @param boolean $waitFlush
1111. * @param boolean $waitSearcher
1112. * @param float $timeout Maximum expected duration of the commit operation on the server (otherwise, will throw a communication exception)
1113. * @return Apache_Solr_Response
1114. *
1115. * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
1116. */
1117. public function optimize($waitFlush = true, $waitSearcher = true, $timeout = 3600)
1118. {
1119. $flushValue = $waitFlush ? 'true' : 'false';
1120. $searcherValue = $waitSearcher ? 'true' : 'false';
1122. $rawPost = '<optimize waitFlush="' . $flushValue . '" waitSearcher="' . $searcherValue . '" />';
1124. return $this->_sendRawPost($this->_updateUrl, $rawPost, $timeout);
1125. }
1127. /**
1128. * Simple Search interface
1129. *
1130. * @param string $query The raw query string
1131. * @param int $offset The starting offset for result documents
1132. * @param int $limit The maximum number of result documents to return
1133. * @param array $params key / value pairs for other query parameters (see Solr documentation), use arrays for parameter keys used more than once (e.g. facet.field)
1134. * @param string $method The HTTP method (Apache_Solr_Service::METHOD_GET or Apache_Solr_Service::METHOD::POST)
1135. * @return Apache_Solr_Response
1136. *
1137. * @throws Apache_Solr_HttpTransportException If an error occurs during the service call
1138. * @throws Apache_Solr_InvalidArgumentException If an invalid HTTP method is used
1139. */
1140. public function search($query, $offset = 0, $limit = 10, $params = array(), $method = self::METHOD_GET)
1141. {
1142. // ensure params is an array
1143. if (!is_null($params))
1144. {
1145. if (!is_array($params))
1146. {
1147. // params was specified but was not an array - invalid
1148. throw new Apache_Solr_InvalidArgumentException("\$params must be a valid array or null");
1149. }
1150. }
1151. else
1152. {
1153. $params = array();
1154. }
1156. // construct our full parameters
1158. // common parameters in this interface
1159. $params['wt'] = self::SOLR_WRITER;
1160. $params['json.nl'] = $this->_namedListTreatment;
1162. $params['q'] = $query;
1163. $params['start'] = $offset;
1164. $params['rows'] = $limit;
1166. $queryString = $this->_generateQueryString($params);
1168. if ($method == self::METHOD_GET)
1169. {
1170. return $this->_sendRawGet($this->_searchUrl . $this->_queryDelimiter . $queryString);
1171. }
1172. else if ($method == self::METHOD_POST)
1173. {
1174. return $this->_sendRawPost($this->_searchUrl, $queryString, FALSE, 'application/x-www-form-urlencoded; charset=UTF-8');
1175. }
1176. else
1177. {
1178. throw new Apache_Solr_InvalidArgumentException("Unsupported method '$method', please use the Apache_Solr_Service::METHOD_* constants");
1179. }
1180. }
1181. }

Documentation generated on Wed, 04 May 2011 11:01:19 -0400 by phpDocumentor 1.4.3