| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834 |
- <?php
- namespace Elastica;
- use Elastica\Bulk\Action;
- use Elastica\Exception\ConnectionException;
- use Elastica\Exception\InvalidException;
- use Elastica\Script\AbstractScript;
- use Elasticsearch\Endpoints\AbstractEndpoint;
- use Elasticsearch\Endpoints\Indices\ForceMerge;
- use Elasticsearch\Endpoints\Indices\Refresh;
- use Elasticsearch\Endpoints\Update;
- use Psr\Log\LoggerInterface;
- use Psr\Log\NullLogger;
- /**
- * Client to connect the the elasticsearch server.
- *
- * @author Nicolas Ruflin <spam@ruflin.com>
- */
- class Client
- {
- /**
- * Config with defaults.
- *
- * log: Set to true, to enable logging, set a string to log to a specific file
- * retryOnConflict: Use in \Elastica\Client::updateDocument
- * bigintConversion: Set to true to enable the JSON bigint to string conversion option (see issue #717)
- *
- * @var array
- */
- protected $_config = [
- 'host' => null,
- 'port' => null,
- 'path' => null,
- 'url' => null,
- 'proxy' => null,
- 'transport' => null,
- 'persistent' => true,
- 'timeout' => null,
- 'connections' => [], // host, port, path, timeout, transport, compression, persistent, timeout, username, password, config -> (curl, headers, url)
- 'roundRobin' => false,
- 'log' => false,
- 'retryOnConflict' => 0,
- 'bigintConversion' => false,
- 'username' => null,
- 'password' => null,
- ];
- /**
- * @var callback
- */
- protected $_callback;
- /**
- * @var Connection\ConnectionPool
- */
- protected $_connectionPool;
- /**
- * @var \Elastica\Request|null
- */
- protected $_lastRequest;
- /**
- * @var \Elastica\Response|null
- */
- protected $_lastResponse;
- /**
- * @var LoggerInterface
- */
- protected $_logger;
- /**
- * @var string
- */
- protected $_version;
- /**
- * Creates a new Elastica client.
- *
- * @param array $config OPTIONAL Additional config options
- * @param callback $callback OPTIONAL Callback function which can be used to be notified about errors (for example connection down)
- * @param LoggerInterface $logger
- */
- public function __construct(array $config = [], $callback = null, LoggerInterface $logger = null)
- {
- $this->_callback = $callback;
- if (!$logger && isset($config['log']) && $config['log']) {
- $logger = new Log($config['log']);
- }
- $this->_logger = $logger ?: new NullLogger();
- $this->setConfig($config);
- $this->_initConnections();
- }
- /**
- * Get current version.
- *
- * @return string
- */
- public function getVersion()
- {
- if ($this->_version) {
- return $this->_version;
- }
- $data = $this->request('/')->getData();
- return $this->_version = $data['version']['number'];
- }
- /**
- * Inits the client connections.
- */
- protected function _initConnections()
- {
- $connections = [];
- foreach ($this->getConfig('connections') as $connection) {
- $connections[] = Connection::create($this->_prepareConnectionParams($connection));
- }
- if (isset($this->_config['servers'])) {
- foreach ($this->getConfig('servers') as $server) {
- $connections[] = Connection::create($this->_prepareConnectionParams($server));
- }
- }
- // If no connections set, create default connection
- if (empty($connections)) {
- $connections[] = Connection::create($this->_prepareConnectionParams($this->getConfig()));
- }
- if (!isset($this->_config['connectionStrategy'])) {
- if (true === $this->getConfig('roundRobin')) {
- $this->setConfigValue('connectionStrategy', 'RoundRobin');
- } else {
- $this->setConfigValue('connectionStrategy', 'Simple');
- }
- }
- $strategy = Connection\Strategy\StrategyFactory::create($this->getConfig('connectionStrategy'));
- $this->_connectionPool = new Connection\ConnectionPool($connections, $strategy, $this->_callback);
- }
- /**
- * Creates a Connection params array from a Client or server config array.
- *
- * @param array $config
- *
- * @return array
- */
- protected function _prepareConnectionParams(array $config)
- {
- $params = [];
- $params['config'] = [];
- foreach ($config as $key => $value) {
- if (in_array($key, ['bigintConversion', 'curl', 'headers', 'url'])) {
- $params['config'][$key] = $value;
- } else {
- $params[$key] = $value;
- }
- }
- return $params;
- }
- /**
- * Sets specific config values (updates and keeps default values).
- *
- * @param array $config Params
- *
- * @return $this
- */
- public function setConfig(array $config)
- {
- foreach ($config as $key => $value) {
- $this->_config[$key] = $value;
- }
- return $this;
- }
- /**
- * Returns a specific config key or the whole
- * config array if not set.
- *
- * @param string $key Config key
- *
- * @throws \Elastica\Exception\InvalidException
- *
- * @return array|string Config value
- */
- public function getConfig($key = '')
- {
- if (empty($key)) {
- return $this->_config;
- }
- if (!array_key_exists($key, $this->_config)) {
- throw new InvalidException('Config key is not set: '.$key);
- }
- return $this->_config[$key];
- }
- /**
- * Sets / overwrites a specific config value.
- *
- * @param string $key Key to set
- * @param mixed $value Value
- *
- * @return $this
- */
- public function setConfigValue($key, $value)
- {
- return $this->setConfig([$key => $value]);
- }
- /**
- * @param array|string $keys config key or path of config keys
- * @param mixed $default default value will be returned if key was not found
- *
- * @return mixed
- */
- public function getConfigValue($keys, $default = null)
- {
- $value = $this->_config;
- foreach ((array) $keys as $key) {
- if (isset($value[$key])) {
- $value = $value[$key];
- } else {
- return $default;
- }
- }
- return $value;
- }
- /**
- * Returns the index for the given connection.
- *
- * @param string $name Index name to create connection to
- *
- * @return \Elastica\Index Index for the given name
- */
- public function getIndex($name)
- {
- return new Index($this, $name);
- }
- /**
- * Adds a HTTP Header.
- *
- * @param string $header The HTTP Header
- * @param string $headerValue The HTTP Header Value
- *
- * @throws \Elastica\Exception\InvalidException If $header or $headerValue is not a string
- *
- * @return $this
- */
- public function addHeader($header, $headerValue)
- {
- if (is_string($header) && is_string($headerValue)) {
- $this->_config['headers'][$header] = $headerValue;
- } else {
- throw new InvalidException('Header must be a string');
- }
- return $this;
- }
- /**
- * Remove a HTTP Header.
- *
- * @param string $header The HTTP Header to remove
- *
- * @throws \Elastica\Exception\InvalidException If $header is not a string
- *
- * @return $this
- */
- public function removeHeader($header)
- {
- if (is_string($header)) {
- if (array_key_exists($header, $this->_config['headers'])) {
- unset($this->_config['headers'][$header]);
- }
- } else {
- throw new InvalidException('Header must be a string');
- }
- return $this;
- }
- /**
- * Uses _bulk to send documents to the server.
- *
- * Array of \Elastica\Document as input. Index and type has to be
- * set inside the document, because for bulk settings documents,
- * documents can belong to any type and index
- *
- * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
- *
- * @param array|\Elastica\Document[] $docs Array of Elastica\Document
- * @param array $requestParams
- *
- * @throws \Elastica\Exception\InvalidException If docs is empty
- *
- * @return \Elastica\Bulk\ResponseSet Response object
- */
- public function updateDocuments(array $docs, array $requestParams = [])
- {
- if (empty($docs)) {
- throw new InvalidException('Array has to consist of at least one element');
- }
- $bulk = new Bulk($this);
- $bulk->addDocuments($docs, Action::OP_TYPE_UPDATE);
- foreach ($requestParams as $key => $value) {
- $bulk->setRequestParam($key, $value);
- }
- return $bulk->send();
- }
- /**
- * Uses _bulk to send documents to the server.
- *
- * Array of \Elastica\Document as input. Index and type has to be
- * set inside the document, because for bulk settings documents,
- * documents can belong to any type and index
- *
- * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
- *
- * @param array|\Elastica\Document[] $docs Array of Elastica\Document
- * @param array $requestParams
- *
- * @throws \Elastica\Exception\InvalidException If docs is empty
- *
- * @return \Elastica\Bulk\ResponseSet Response object
- */
- public function addDocuments(array $docs, array $requestParams = [])
- {
- if (empty($docs)) {
- throw new InvalidException('Array has to consist of at least one element');
- }
- $bulk = new Bulk($this);
- $bulk->addDocuments($docs);
- foreach ($requestParams as $key => $value) {
- $bulk->setRequestParam($key, $value);
- }
- return $bulk->send();
- }
- /**
- * Update document, using update script. Requires elasticsearch >= 0.19.0.
- *
- * @param int|string $id document id
- * @param array|\Elastica\Script\AbstractScript|\Elastica\Document $data raw data for request body
- * @param string $index index to update
- * @param string $type type of index to update
- * @param array $options array of query params to use for query. For possible options check es api
- *
- * @return \Elastica\Response
- *
- * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html
- */
- public function updateDocument($id, $data, $index, $type, array $options = [])
- {
- $endpoint = new Update();
- $endpoint->setID($id);
- $endpoint->setIndex($index);
- $endpoint->setType($type);
- if ($data instanceof AbstractScript) {
- $requestData = $data->toArray();
- } elseif ($data instanceof Document) {
- $requestData = ['doc' => $data->getData()];
- if ($data->getDocAsUpsert()) {
- $requestData['doc_as_upsert'] = true;
- }
- $docOptions = $data->getOptions(
- [
- 'version',
- 'version_type',
- 'routing',
- 'percolate',
- 'parent',
- 'fields',
- 'retry_on_conflict',
- 'consistency',
- 'replication',
- 'refresh',
- 'timeout',
- ]
- );
- $options += $docOptions;
- // set fields param to source only if options was not set before
- if ($data instanceof Document && ($data->isAutoPopulate()
- || $this->getConfigValue(['document', 'autoPopulate'], false))
- && !isset($options['fields'])
- ) {
- $options['fields'] = '_source';
- }
- } else {
- $requestData = $data;
- }
- //If an upsert document exists
- if ($data instanceof AbstractScript || $data instanceof Document) {
- if ($data->hasUpsert()) {
- $requestData['upsert'] = $data->getUpsert()->getData();
- }
- }
- if (!isset($options['retry_on_conflict'])) {
- if ($retryOnConflict = $this->getConfig('retryOnConflict')) {
- $options['retry_on_conflict'] = $retryOnConflict;
- }
- }
- $endpoint->setBody($requestData);
- $endpoint->setParams($options);
- $response = $this->requestEndpoint($endpoint);
- if ($response->isOk()
- && $data instanceof Document
- && ($data->isAutoPopulate() || $this->getConfigValue(['document', 'autoPopulate'], false))
- ) {
- $responseData = $response->getData();
- if (isset($responseData['_version'])) {
- $data->setVersion($responseData['_version']);
- }
- if (isset($options['fields'])) {
- $this->_populateDocumentFieldsFromResponse($response, $data, $options['fields']);
- }
- }
- return $response;
- }
- /**
- * @param \Elastica\Response $response
- * @param \Elastica\Document $document
- * @param string $fields Array of field names to be populated or '_source' if whole document data should be updated
- */
- protected function _populateDocumentFieldsFromResponse(Response $response, Document $document, $fields)
- {
- $responseData = $response->getData();
- if ('_source' == $fields) {
- if (isset($responseData['get']['_source']) && is_array($responseData['get']['_source'])) {
- $document->setData($responseData['get']['_source']);
- }
- } else {
- $keys = explode(',', $fields);
- $data = $document->getData();
- foreach ($keys as $key) {
- if (isset($responseData['get']['fields'][$key])) {
- $data[$key] = $responseData['get']['fields'][$key];
- } elseif (isset($data[$key])) {
- unset($data[$key]);
- }
- }
- $document->setData($data);
- }
- }
- /**
- * Bulk deletes documents.
- *
- * @param array|\Elastica\Document[] $docs
- * @param array $requestParams
- *
- * @throws \Elastica\Exception\InvalidException
- *
- * @return \Elastica\Bulk\ResponseSet
- */
- public function deleteDocuments(array $docs, array $requestParams = [])
- {
- if (empty($docs)) {
- throw new InvalidException('Array has to consist of at least one element');
- }
- $bulk = new Bulk($this);
- $bulk->addDocuments($docs, Action::OP_TYPE_DELETE);
- foreach ($requestParams as $key => $value) {
- $bulk->setRequestParam($key, $value);
- }
- return $bulk->send();
- }
- /**
- * Returns the status object for all indices.
- *
- * @return \Elastica\Status Status object
- */
- public function getStatus()
- {
- return new Status($this);
- }
- /**
- * Returns the current cluster.
- *
- * @return \Elastica\Cluster Cluster object
- */
- public function getCluster()
- {
- return new Cluster($this);
- }
- /**
- * Establishes the client connections.
- */
- public function connect()
- {
- return $this->_initConnections();
- }
- /**
- * @param \Elastica\Connection $connection
- *
- * @return $this
- */
- public function addConnection(Connection $connection)
- {
- $this->_connectionPool->addConnection($connection);
- return $this;
- }
- /**
- * Determines whether a valid connection is available for use.
- *
- * @return bool
- */
- public function hasConnection()
- {
- return $this->_connectionPool->hasConnection();
- }
- /**
- * @throws \Elastica\Exception\ClientException
- *
- * @return \Elastica\Connection
- */
- public function getConnection()
- {
- return $this->_connectionPool->getConnection();
- }
- /**
- * @return \Elastica\Connection[]
- */
- public function getConnections()
- {
- return $this->_connectionPool->getConnections();
- }
- /**
- * @return \Elastica\Connection\Strategy\StrategyInterface
- */
- public function getConnectionStrategy()
- {
- return $this->_connectionPool->getStrategy();
- }
- /**
- * @param array|\Elastica\Connection[] $connections
- *
- * @return $this
- */
- public function setConnections(array $connections)
- {
- $this->_connectionPool->setConnections($connections);
- return $this;
- }
- /**
- * Deletes documents with the given ids, index, type from the index.
- *
- * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
- *
- * @param array $ids Document ids
- * @param string|\Elastica\Index $index Index name
- * @param string|\Elastica\Type $type Type of documents
- * @param string|bool $routing Optional routing key for all ids
- *
- * @throws \Elastica\Exception\InvalidException
- *
- * @return \Elastica\Bulk\ResponseSet Response object
- */
- public function deleteIds(array $ids, $index, $type, $routing = false)
- {
- if (empty($ids)) {
- throw new InvalidException('Array has to consist of at least one id');
- }
- $bulk = new Bulk($this);
- $bulk->setIndex($index);
- $bulk->setType($type);
- foreach ($ids as $id) {
- $action = new Action(Action::OP_TYPE_DELETE);
- $action->setId($id);
- if (!empty($routing)) {
- $action->setRouting($routing);
- }
- $bulk->addAction($action);
- }
- return $bulk->send();
- }
- /**
- * Bulk operation.
- *
- * Every entry in the params array has to exactly on array
- * of the bulk operation. An example param array would be:
- *
- * array(
- * array('index' => array('_index' => 'test', '_type' => 'user', '_id' => '1')),
- * array('user' => array('name' => 'hans')),
- * array('delete' => array('_index' => 'test', '_type' => 'user', '_id' => '2'))
- * );
- *
- * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html
- *
- * @param array $params Parameter array
- *
- * @throws \Elastica\Exception\ResponseException
- * @throws \Elastica\Exception\InvalidException
- *
- * @return \Elastica\Bulk\ResponseSet Response object
- */
- public function bulk(array $params)
- {
- if (empty($params)) {
- throw new InvalidException('Array has to consist of at least one param');
- }
- $bulk = new Bulk($this);
- $bulk->addRawData($params);
- return $bulk->send();
- }
- /**
- * Makes calls to the elasticsearch server based on this index.
- *
- * It's possible to make any REST query directly over this method
- *
- * @param string $path Path to call
- * @param string $method Rest method to use (GET, POST, DELETE, PUT)
- * @param array|string $data OPTIONAL Arguments as array or pre-encoded string
- * @param array $query OPTIONAL Query params
- * @param string $contentType Content-Type sent with this request
- *
- * @throws Exception\ConnectionException|Exception\ClientException
- *
- * @return Response Response object
- */
- public function request($path, $method = Request::GET, $data = [], array $query = [], $contentType = Request::DEFAULT_CONTENT_TYPE)
- {
- $connection = $this->getConnection();
- $request = $this->_lastRequest = new Request($path, $method, $data, $query, $connection, $contentType);
- $this->_lastResponse = null;
- try {
- $response = $this->_lastResponse = $request->send();
- } catch (ConnectionException $e) {
- $this->_connectionPool->onFail($connection, $e, $this);
- $this->_log($e);
- // In case there is no valid connection left, throw exception which caused the disabling of the connection.
- if (!$this->hasConnection()) {
- throw $e;
- }
- return $this->request($path, $method, $data, $query);
- }
- $this->_log($request);
- return $response;
- }
- /**
- * Makes calls to the elasticsearch server with usage official client Endpoint.
- *
- * @param AbstractEndpoint $endpoint
- *
- * @return Response
- */
- public function requestEndpoint(AbstractEndpoint $endpoint)
- {
- return $this->request(
- ltrim($endpoint->getURI(), '/'),
- $endpoint->getMethod(),
- null === $endpoint->getBody() ? [] : $endpoint->getBody(),
- $endpoint->getParams()
- );
- }
- /**
- * logging.
- *
- * @deprecated Overwriting Client->_log is deprecated. Handle logging functionality by using a custom LoggerInterface.
- *
- * @param mixed $context
- */
- protected function _log($context)
- {
- if ($context instanceof ConnectionException) {
- $this->_logger->error('Elastica Request Failure', [
- 'exception' => $context,
- 'request' => $context->getRequest()->toArray(),
- 'retry' => $this->hasConnection(),
- ]);
- return;
- }
- if ($context instanceof Request) {
- $this->_logger->debug('Elastica Request', [
- 'request' => $context->toArray(),
- 'response' => $this->_lastResponse ? $this->_lastResponse->getData() : null,
- 'responseStatus' => $this->_lastResponse ? $this->_lastResponse->getStatus() : null,
- ]);
- return;
- }
- $this->_logger->debug('Elastica Request', [
- 'message' => $context,
- ]);
- }
- /**
- * Optimizes all search indices.
- *
- * @param array $args OPTIONAL Optional arguments
- *
- * @return \Elastica\Response Response object
- *
- * @deprecated Replaced by forcemergeAll
- * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-optimize.html
- */
- public function optimizeAll($args = [])
- {
- trigger_error('Deprecated: Elastica\Client::optimizeAll() is deprecated and will be removed in further Elastica releases. Use Elastica\Client::forcemergeAll() instead.', E_USER_DEPRECATED);
- return $this->forcemergeAll($args);
- }
- /**
- * Force merges all search indices.
- *
- * @param array $args OPTIONAL Optional arguments
- *
- * @return \Elastica\Response Response object
- *
- * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html
- */
- public function forcemergeAll($args = [])
- {
- $endpoint = new ForceMerge();
- $endpoint->setParams($args);
- return $this->requestEndpoint($endpoint);
- }
- /**
- * Refreshes all search indices.
- *
- * @return \Elastica\Response Response object
- *
- * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html
- */
- public function refreshAll()
- {
- return $this->requestEndpoint(new Refresh());
- }
- /**
- * @return Request|null
- */
- public function getLastRequest()
- {
- return $this->_lastRequest;
- }
- /**
- * @return Response|null
- */
- public function getLastResponse()
- {
- return $this->_lastResponse;
- }
- /**
- * Replace the existing logger.
- *
- * @param LoggerInterface $logger
- *
- * @return $this
- */
- public function setLogger(LoggerInterface $logger)
- {
- $this->_logger = $logger;
- return $this;
- }
- }
|