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