1<?php 2 3namespace Elastica\Index; 4 5use Elastica\Exception\NotFoundException; 6use Elastica\Exception\ResponseException; 7use Elastica\Index as BaseIndex; 8use Elastica\Request; 9use Elastica\Response; 10 11/** 12 * Elastica index settings object. 13 * 14 * All settings listed in the update settings API (https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html) 15 * can be changed on a running indices. To make changes like the merge policy (https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-merge.html) 16 * the index has to be closed first and reopened after the call 17 * 18 * @author Nicolas Ruflin <spam@ruflin.com> 19 * 20 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html 21 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-merge.html 22 */ 23class Settings 24{ 25 const DEFAULT_REFRESH_INTERVAL = '1s'; 26 27 const DEFAULT_NUMBER_OF_REPLICAS = 1; 28 29 const DEFAULT_NUMBER_OF_SHARDS = 1; 30 31 /** 32 * Response. 33 * 34 * @var Response Response object 35 */ 36 protected $_response; 37 38 /** 39 * Stats info. 40 * 41 * @var array Stats info 42 */ 43 protected $_data = []; 44 45 /** 46 * Index. 47 * 48 * @var BaseIndex Index object 49 */ 50 protected $_index; 51 52 /** 53 * Construct. 54 * 55 * @param BaseIndex $index Index object 56 */ 57 public function __construct(BaseIndex $index) 58 { 59 $this->_index = $index; 60 } 61 62 /** 63 * Returns the current settings of the index. 64 * 65 * If param is set, only specified setting is return. 66 * 'index.' is added in front of $setting. 67 * 68 * @param string $setting OPTIONAL Setting name to return 69 * 70 * @return array|string|null Settings data 71 * 72 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html 73 */ 74 public function get(string $setting = '') 75 { 76 $requestData = $this->request()->getData(); 77 $data = \reset($requestData); 78 79 if (empty($data['settings']) || empty($data['settings']['index'])) { 80 // should not append, the request should throw a ResponseException 81 throw new NotFoundException('Index '.$this->getIndex()->getName().' not found'); 82 } 83 $settings = $data['settings']['index']; 84 85 if (!$setting) { 86 // return all array 87 return $settings; 88 } 89 90 if (isset($settings[$setting])) { 91 return $settings[$setting]; 92 } 93 94 if (false !== \strpos($setting, '.')) { 95 // translate old dot-notation settings to nested arrays 96 $keys = \explode('.', $setting); 97 foreach ($keys as $key) { 98 if (isset($settings[$key])) { 99 $settings = $settings[$key]; 100 } else { 101 return null; 102 } 103 } 104 105 return $settings; 106 } 107 108 return null; 109 } 110 111 /** 112 * Returns a setting interpreted as a bool. 113 * 114 * One can use a real bool, int(0), int(1) to set bool settings. 115 * But Elasticsearch stores and returns all settings as strings and does 116 * not normalize bool values. This method ensures a bool is returned for 117 * whichever string representation is used like 'true', '1', 'on', 'yes'. 118 * 119 * @param string $setting Setting name to return 120 * 121 * @return bool 122 */ 123 public function getBool(string $setting): bool 124 { 125 $data = $this->get($setting); 126 127 return 'true' === $data || '1' === $data || 'on' === $data || 'yes' === $data; 128 } 129 130 /** 131 * Sets the number of replicas. 132 * 133 * @param int $replicas Number of replicas 134 * 135 * @return Response Response object 136 */ 137 public function setNumberOfReplicas(int $replicas): Response 138 { 139 return $this->set(['number_of_replicas' => $replicas]); 140 } 141 142 /** 143 * Returns the number of replicas. 144 * 145 * If no number of replicas is set, the default number is returned 146 * 147 * @return int The number of replicas 148 */ 149 public function getNumberOfReplicas(): int 150 { 151 return $this->get('number_of_replicas') ?? self::DEFAULT_NUMBER_OF_REPLICAS; 152 } 153 154 /** 155 * Returns the number of shards. 156 * 157 * If no number of shards is set, the default number is returned 158 * 159 * @return int The number of shards 160 */ 161 public function getNumberOfShards(): int 162 { 163 return $this->get('number_of_shards') ?? self::DEFAULT_NUMBER_OF_SHARDS; 164 } 165 166 /** 167 * Sets the index to read only. 168 * 169 * @param bool $readOnly (default = true) 170 * 171 * @return Response 172 */ 173 public function setReadOnly(bool $readOnly = true): Response 174 { 175 return $this->set(['blocks.read_only' => $readOnly]); 176 } 177 178 /** 179 * @return bool 180 */ 181 public function getReadOnly(): bool 182 { 183 return $this->getBool('blocks.read_only'); 184 } 185 186 /** 187 * @return bool 188 */ 189 public function getBlocksRead(): bool 190 { 191 return $this->getBool('blocks.read'); 192 } 193 194 /** 195 * @param bool $state OPTIONAL (default = true) 196 * 197 * @return Response 198 */ 199 public function setBlocksRead(bool $state = true): Response 200 { 201 return $this->set(['blocks.read' => $state]); 202 } 203 204 /** 205 * @return bool 206 */ 207 public function getBlocksWrite(): bool 208 { 209 return $this->getBool('blocks.write'); 210 } 211 212 /** 213 * @param bool $state OPTIONAL (default = true) 214 * 215 * @return Response 216 */ 217 public function setBlocksWrite(bool $state = true): Response 218 { 219 return $this->set(['blocks.write' => $state]); 220 } 221 222 /** 223 * @return bool 224 */ 225 public function getBlocksMetadata(): bool 226 { 227 // When blocks.metadata is enabled, reading the settings is not possible anymore. 228 // So when a cluster_block_exception happened it must be enabled. 229 try { 230 return $this->getBool('blocks.metadata'); 231 } catch (ResponseException $e) { 232 if ('cluster_block_exception' === $e->getResponse()->getFullError()['type']) { 233 return true; 234 } 235 236 throw $e; 237 } 238 } 239 240 /** 241 * Set to true to disable index metadata reads and writes. 242 * 243 * @param bool $state OPTIONAL (default = true) 244 * 245 * @return Response 246 */ 247 public function setBlocksMetadata(bool $state = true): Response 248 { 249 return $this->set(['blocks.metadata' => $state]); 250 } 251 252 /** 253 * Sets the index refresh interval. 254 * 255 * Value can be for example 3s for 3 seconds or 256 * 5m for 5 minutes. -1 to disabled refresh. 257 * 258 * @param string $interval Duration of the refresh interval 259 * 260 * @return Response Response object 261 */ 262 public function setRefreshInterval(string $interval): Response 263 { 264 return $this->set(['refresh_interval' => $interval]); 265 } 266 267 /** 268 * Returns the refresh interval. 269 * 270 * If no interval is set, the default interval is returned 271 * 272 * @return string Refresh interval 273 */ 274 public function getRefreshInterval(): string 275 { 276 return $this->get('refresh_interval') ?? self::DEFAULT_REFRESH_INTERVAL; 277 } 278 279 /** 280 * Sets the specific merge policies. 281 * 282 * To have this changes made the index has to be closed and reopened 283 * 284 * @param string $key Merge policy key (for ex. expunge_deletes_allowed) 285 * @param string $value 286 * 287 * @return Response 288 * 289 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-merge.html 290 */ 291 public function setMergePolicy(string $key, string $value): Response 292 { 293 $this->_index->close(); 294 $response = $this->set(['merge.policy.'.$key => $value]); 295 $this->_index->open(); 296 297 return $response; 298 } 299 300 /** 301 * Returns the specific merge policy value. 302 * 303 * @param string $key Merge policy key (for ex. expunge_deletes_allowed) 304 * 305 * @return string Refresh interval 306 * 307 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-merge.html 308 */ 309 public function getMergePolicy(string $key) 310 { 311 $settings = $this->get(); 312 313 return $settings['merge']['policy'][$key] ?? null; 314 } 315 316 /** 317 * Can be used to set/update settings. 318 * 319 * @param array $data Arguments 320 * 321 * @return Response Response object 322 */ 323 public function set(array $data): Response 324 { 325 return $this->request($data, Request::PUT); 326 } 327 328 /** 329 * Returns the index object. 330 * 331 * @return BaseIndex Index object 332 */ 333 public function getIndex(): BaseIndex 334 { 335 return $this->_index; 336 } 337 338 /** 339 * Updates the given settings for the index. 340 * 341 * With elasticsearch 0.16 the following settings are supported 342 * - index.term_index_interval 343 * - index.term_index_divisor 344 * - index.translog.flush_threshold_ops 345 * - index.translog.flush_threshold_size 346 * - index.translog.flush_threshold_period 347 * - index.refresh_interval 348 * - index.merge.policy 349 * - index.auto_expand_replicas 350 * 351 * @param array $data OPTIONAL Data array 352 * @param string $method OPTIONAL Transfer method (default = \Elastica\Request::GET) 353 * 354 * @return Response Response object 355 */ 356 public function request(array $data = [], string $method = Request::GET): Response 357 { 358 $path = '_settings'; 359 360 if (!empty($data)) { 361 $data = ['index' => $data]; 362 } 363 364 return $this->getIndex()->request($path, $method, $data); 365 } 366} 367