1<?php 2 3namespace Elastica\Multi; 4 5use Elastica\Client; 6use Elastica\JSON; 7use Elastica\Request; 8use Elastica\Search as BaseSearch; 9 10/** 11 * Elastica multi search. 12 * 13 * @author munkie 14 * 15 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-multi-search.html 16 */ 17class Search 18{ 19 /** 20 * @var Client 21 */ 22 protected $_client; 23 24 /** 25 * @var array 26 */ 27 protected $_options = []; 28 29 /** 30 * @var BaseSearch[] 31 */ 32 protected $_searches = []; 33 /** 34 * @const string[] valid header options 35 */ 36 private static $HEADER_OPTIONS = [ 37 'index', 38 'types', 39 'search_type', 40 'routing', 41 'preference', 42 ]; 43 44 /** 45 * @var MultiBuilderInterface 46 */ 47 private $_builder; 48 49 public function __construct(Client $client, ?MultiBuilderInterface $builder = null) 50 { 51 $this->_builder = $builder ?? new MultiBuilder(); 52 $this->_client = $client; 53 } 54 55 public function getClient(): Client 56 { 57 return $this->_client; 58 } 59 60 /** 61 * @return $this 62 */ 63 public function clearSearches(): self 64 { 65 $this->_searches = []; 66 67 return $this; 68 } 69 70 /** 71 * @return $this 72 */ 73 public function addSearch(BaseSearch $search, ?string $key = null): self 74 { 75 if ($key) { 76 $this->_searches[$key] = $search; 77 } else { 78 $this->_searches[] = $search; 79 } 80 81 return $this; 82 } 83 84 /** 85 * @param BaseSearch[] $searches 86 * 87 * @return $this 88 */ 89 public function addSearches(array $searches): self 90 { 91 foreach ($searches as $key => $search) { 92 $this->addSearch($search, $key); 93 } 94 95 return $this; 96 } 97 98 /** 99 * @param BaseSearch[] $searches 100 * 101 * @return $this 102 */ 103 public function setSearches(array $searches): self 104 { 105 $this->clearSearches(); 106 $this->addSearches($searches); 107 108 return $this; 109 } 110 111 /** 112 * @return BaseSearch[] 113 */ 114 public function getSearches(): array 115 { 116 return $this->_searches; 117 } 118 119 /** 120 * @return $this 121 */ 122 public function setSearchType(string $searchType): self 123 { 124 $this->_options[BaseSearch::OPTION_SEARCH_TYPE] = $searchType; 125 126 return $this; 127 } 128 129 public function search(): ResultSet 130 { 131 $data = $this->_getData(); 132 133 $response = $this->getClient()->request( 134 '_msearch', 135 Request::POST, 136 $data, 137 $this->_options, 138 Request::NDJSON_CONTENT_TYPE 139 ); 140 141 return $this->_builder->buildMultiResultSet($response, $this->getSearches()); 142 } 143 144 protected function _getData(): string 145 { 146 $data = ''; 147 foreach ($this->getSearches() as $search) { 148 $data .= $this->_getSearchData($search); 149 } 150 151 return $data; 152 } 153 154 protected function _getSearchData(BaseSearch $search): string 155 { 156 $header = $this->_getSearchDataHeader($search); 157 158 $header = (empty($header)) ? new \stdClass() : $header; 159 $query = $search->getQuery(); 160 161 // Keep other query options as part of the search body 162 $queryOptions = \array_diff_key($search->getOptions(), \array_flip(self::$HEADER_OPTIONS)); 163 164 $data = JSON::stringify($header)."\n"; 165 $data .= JSON::stringify($query->toArray() + $queryOptions)."\n"; 166 167 return $data; 168 } 169 170 protected function _getSearchDataHeader(BaseSearch $search): array 171 { 172 $header = $search->getOptions(); 173 174 if ($search->hasIndices()) { 175 $header['index'] = $search->getIndices(); 176 } 177 178 // Filter options accepted in the "header" 179 return \array_intersect_key($header, \array_flip(self::$HEADER_OPTIONS)); 180 } 181} 182