1[[configuration]]
2== Configuration
3
4Almost every aspect of the client is configurable.  Most users will only need to configure a few parameters to suit
5their needs, but it is possible to completely replace much of the internals if required.
6
7Custom configuration is accomplished before the client is instantiated, through the ClientBuilder helper object.
8We'll walk through all the configuration options and show sample code to replace the various components.
9
10=== Inline Host Configuration
11
12The most common configuration is telling the client about your cluster: how many nodes, their addresses and ports.  If
13no hosts are specified, the client will attempt to connect to `localhost:9200`.
14
15This behavior can be changed by using the `setHosts()` method on `ClientBuilder`.  The method accepts an array of values,
16each entry corresponding to one node in your cluster.  The format of the host can vary, depending on your needs (ip vs
17hostname, port, ssl, etc)
18
19[source,php]
20----
21$hosts = [
22    '192.168.1.1:9200',         // IP + Port
23    '192.168.1.2',              // Just IP
24    'mydomain.server.com:9201', // Domain + Port
25    'mydomain2.server.com',     // Just Domain
26    'https://localhost',        // SSL to localhost
27    'https://192.168.1.3:9200'  // SSL to IP + Port
28];
29$client = ClientBuilder::create()           // Instantiate a new ClientBuilder
30                    ->setHosts($hosts)      // Set the hosts
31                    ->build();              // Build the client object
32----
33
34Notice that the `ClientBuilder` object allows chaining method calls for brevity.  It is also possible to call the methods
35individually:
36
37[source,php]
38----
39$hosts = [
40    '192.168.1.1:9200',         // IP + Port
41    '192.168.1.2',              // Just IP
42    'mydomain.server.com:9201', // Domain + Port
43    'mydomain2.server.com',     // Just Domain
44    'https://localhost',        // SSL to localhost
45    'https://192.168.1.3:9200'  // SSL to IP + Port
46];
47$clientBuilder = ClientBuilder::create();   // Instantiate a new ClientBuilder
48$clientBuilder->setHosts($hosts);           // Set the hosts
49$client = $clientBuilder->build();          // Build the client object
50----
51
52=== Extended Host Configuration
53
54The client also supports an _extended_ host configuration syntax.  The inline configuration method relies on PHP's
55`filter_var()` and `parse_url()` methods to validate and extract the components of a URL.  Unfortunately, these built-in
56methods run into problems with certain edge-cases.  For example, `filter_var()` will not accept URL's that have underscores
57(which are questionably legal, depending on how you interpret the RFCs).  Similarly, `parse_url()` will choke if a
58Basic Auth's password contains special characters such as a pound sign (`#`) or question-marks (`?`).
59
60For this reason, the client supports an extended host syntax which provides greater control over host initialization.
61None of the components are validated, so edge-cases like underscores domain names will not cause problems.
62
63The extended syntax is an array of parameters for each host. The structure of the parameter list is identical to the return values of a http://php.net/manual/en/function.parse-url.php#refsect1-function.parse-url-returnvalues[`parse_url()`] call:
64
65[source,php]
66----
67$hosts = [
68    // This is effectively equal to: "https://username:password!#$?*abc@foo.com:9200/elastic"
69    [
70        'host' => 'foo.com',
71        'port' => '9200',
72        'scheme' => 'https',
73        'path' => '/elastic',
74        'user' => 'username',
75        'pass' => 'password!#$?*abc'
76    ],
77
78    // This is equal to "http://localhost:9200/"
79    [
80        'host' => 'localhost',    // Only host is required
81    ]
82];
83$client = ClientBuilder::create()           // Instantiate a new ClientBuilder
84                    ->setHosts($hosts)      // Set the hosts
85                    ->build();              // Build the client object
86----
87
88Only the `host` parameter is required for each configured host.  If not provided, the default port is `9200`.  The default
89scheme is `http`.
90
91=== Authorization and Encryption
92
93For details about HTTP Authorization and SSL encryption, see
94<<security,Authorization and SSL>>.
95
96=== Set retries
97
98By default, the client will retry `n` times, where `n = number of nodes` in your cluster.  A retry is only performed
99if the operation results in a "hard" exception: connection refusal, connection timeout, DNS lookup timeout, etc.  4xx and
1005xx errors are not considered retry'able events, since the node returns an operational response.
101
102If you would like to disable retries, or change the number, you can do so with the `setRetries()` method:
103
104[source,php]
105----------------------------
106
107$client = ClientBuilder::create()
108                    ->setRetries(2)
109                    ->build();
110----------------------------
111
112When the client runs out of retries, it will throw the last exception that it received.  For example, if you have ten
113alive nodes, and `setRetries(5)`, the client will attempt to execute the command up to five times.  If all five nodes
114result in a connection timeout (for example), the client will throw an `OperationTimeoutException`.  Depending on the
115Connection Pool being used, these nodes may also be marked dead.
116
117To help in identification, exceptions that are thrown due to max retries will wrap a `MaxRetriesException`.  For example,
118you can catch a specific curl exception then check if it wraps a MaxRetriesException using `getPrevious()`:
119
120[source,php]
121----
122$client = Elasticsearch\ClientBuilder::create()
123    ->setHosts(["localhost:1"])
124    ->setRetries(0)
125    ->build();
126
127try {
128    $client->search($searchParams);
129} catch (Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost $e) {
130    $previous = $e->getPrevious();
131    if ($previous instanceof 'Elasticsearch\Common\Exceptions\MaxRetriesException') {
132        echo "Max retries!";
133    }
134}
135----
136
137Alternatively, all "hard" curl exceptions (`CouldNotConnectToHost`, `CouldNotResolveHostException`, `OperationTimeoutException`)
138extend the more general `TransportException`.  So you could instead catch the general `TransportException` and then
139check it's previous value:
140
141[source,php]
142----
143$client = Elasticsearch\ClientBuilder::create()
144    ->setHosts(["localhost:1"])
145    ->setRetries(0)
146    ->build();
147
148try {
149    $client->search($searchParams);
150} catch (Elasticsearch\Common\Exceptions\TransportException $e) {
151    $previous = $e->getPrevious();
152    if ($previous instanceof 'Elasticsearch\Common\Exceptions\MaxRetriesException') {
153        echo "Max retries!";
154    }
155}
156----
157
158
159[[enabling_logger]]
160=== Enabling the Logger
161Elasticsearch-PHP supports logging, but it is not enabled by default for performance reasons.  If you wish to enable logging,
162you need to select a logging implementation, install it, then enable the logger in the Client.  The recommended logger
163is https://github.com/Seldaek/monolog[Monolog], but any logger that implements the `PSR/Log` interface will work.
164
165You might have noticed that Monolog was suggested during installation.  To begin using Monolog, add it to your `composer.json`:
166
167[source,json]
168----------------------------
169{
170    "require": {
171        ...
172        "elasticsearch/elasticsearch" : "~5.0",
173        "monolog/monolog": "~1.0"
174    }
175}
176----------------------------
177
178And then update your composer installation:
179
180[source,shell]
181----------------------------
182php composer.phar update
183----------------------------
184
185Once Monolog (or another logger) is installed, you need to create a log object and inject it into the client:
186
187[source,php]
188----
189use Monolog\Logger;
190use Monolog\Handler\StreamHandler;
191
192$logger = new Logger('name');
193$logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
194
195$client = ClientBuilder::create()       // Instantiate a new ClientBuilder
196            ->setLogger($logger)        // Set your custom logger
197            ->build();                  // Build the client object
198----
199
200
201=== Configure the HTTP Handler
202
203Elasticsearch-PHP uses an interchangeable HTTP transport layer called https://github.com/guzzle/RingPHP/[RingPHP].  This
204allows the client to construct a generic HTTP request, then pass it to the transport layer to execute.  The actual execution
205details are hidden from the client and modular, so that you can choose from several HTTP handlers depending on your needs.
206
207The default handler that the client uses is a combination handler.  When executing in synchronous mode, the handler
208uses `CurlHandler`, which executes single curl calls.  These are very fast for single requests.  When asynchronous (future)
209mode is enabled, the handler switches to `CurlMultiHandler`, which uses the curl_multi interface.  This involves a bit
210more overhead, but allows batches of HTTP requests to be processed in parallel.
211
212You can configure the HTTP handler with one of several helper functions, or provide your own custom handler:
213
214[source,php]
215----
216$defaultHandler = ClientBuilder::defaultHandler();
217$singleHandler  = ClientBuilder::singleHandler();
218$multiHandler   = ClientBuilder::multiHandler();
219$customHandler  = new MyCustomHandler();
220
221$client = ClientBuilder::create()
222            ->setHandler($defaultHandler)
223            ->build();
224----
225
226For details on creating your own custom Ring handler, please see the http://guzzle.readthedocs.org/en/latest/handlers.html[RingPHP Documentation]
227
228The default handler is recommended in almost all cases.  This allows fast synchronous execution, while retaining flexibility
229to invoke parallel batches with async future mode.  You may consider using just the `singleHandler` if you know you will
230never need async capabilities, since it will save a small amount of overhead by reducing indirection.
231
232
233=== Setting the Connection Pool
234
235The client maintains a pool of connections, with each connection representing a node in your cluster.  There are several
236connection pool implementations available, and each has slightly different behavior (pinging vs no pinging, etc).
237Connection pools are configured via the `setConnectionPool()` method:
238
239[source,php]
240----
241$connectionPool = '\Elasticsearch\ConnectionPool\StaticNoPingConnectionPool';
242$client = ClientBuilder::create()
243            ->setConnectionPool($connectionPool)
244            ->build();
245----
246
247For more details, please see the dedicated page on
248<<connection_pool,configuring connection pools>>.
249
250=== Setting the Connection Selector
251
252The connection pool manages the connections to your cluster, but the Selector is the logic that decides which connection
253should be used for the next API request.  There are several selectors that you can choose from.  Selectors can be changed
254via the `setSelector()` method:
255
256[source,php]
257----
258$selector = '\Elasticsearch\ConnectionPool\Selectors\StickyRoundRobinSelector';
259$client = ClientBuilder::create()
260            ->setSelector($selector)
261            ->build();
262----
263
264For more details, please see the dedicated page on
265<<selectors,configuring selectors>>.
266
267
268=== Setting the Serializer
269
270Requests are given to the client in the form of associative arrays, but Elasticsearch expects JSON.  The Serializer's
271job is to serialize PHP objects into JSON.  It also de-serializes JSON back into PHP arrays.  This seems trivial, but
272there are a few edgecases which make it useful for the serializer to remain modular.
273
274The majority of people will never need to change the default serializer (`SmartSerializer`), but if you need to,
275it can be done via the `setSerializer()` method:
276
277[source,php]
278----
279$serializer = '\Elasticsearch\Serializers\SmartSerializer';
280$client = ClientBuilder::create()
281            ->setSerializer($serializer)
282            ->build();
283----
284
285For more details, please see the dedicated page on
286<<serializers,configuring serializers>>.
287
288
289=== Setting a custom ConnectionFactory
290
291The ConnectionFactory instantiates new Connection objects when requested by the ConnectionPool.  A single Connection
292represents a single node.  Since the client hands actual networking work over to RingPHP, the Connection's main job is
293book-keeping:  Is this node alive?  Did it fail a ping request?  What is the host and port?
294
295There is little reason to provide your own ConnectionFactory, but if you need to do so, you need to supply an intact
296ConnectionFactory object to the `setConnectionFactory()` method.  The object should implement the `ConnectionFactoryInterface`
297interface.
298
299[source,php]
300----
301
302class MyConnectionFactory implements ConnectionFactoryInterface
303{
304
305    public function __construct($handler, array $connectionParams,
306                                SerializerInterface $serializer,
307                                LoggerInterface $logger,
308                                LoggerInterface $tracer)
309    {
310       // Code here
311    }
312
313
314    /**
315     * @param $hostDetails
316     *
317     * @return ConnectionInterface
318     */
319    public function create($hostDetails)
320    {
321        // Code here...must return a Connection object
322    }
323}
324
325
326$connectionFactory = new MyConnectionFactory(
327    $handler,
328    $connectionParams,
329    $serializer,
330    $logger,
331    $tracer
332);
333
334$client = ClientBuilder::create()
335            ->setConnectionFactory($connectionFactory);
336            ->build();
337----
338
339As you can see, if you decide to inject your own ConnectionFactory, you take over the responsibiltiy of wiring it correctly.
340The ConnectionFactory requires a working HTTP handler, serializer, logger and tracer.
341
342
343=== Set the Endpoint closure
344
345The client uses an Endpoint closure to dispatch API requests to the correct Endpoint object.  A namespace object will
346construct a new Endpoint via this closure, which means this is a handy location if you wish to extend the available set
347of API endpoints available
348
349For example, we could add a new endpoint like so:
350
351[source,php]
352----
353
354$transport = $this->transport;
355$serializer = $this->serializer;
356
357$newEndpoint = function ($class) use ($transport, $serializer) {
358    if ($class == 'SuperSearch') {
359        return new MyProject\SuperSearch($transport);
360    } else {
361        // Default handler
362        $fullPath = '\\Elasticsearch\\Endpoints\\' . $class;
363        if ($class === 'Bulk' || $class === 'Msearch' || $class === 'MPercolate') {
364            return new $fullPath($transport, $serializer);
365        } else {
366            return new $fullPath($transport);
367        }
368    }
369};
370
371$client = ClientBuilder::create()
372            ->setEndpoint($newEndpoint)
373            ->build();
374----
375
376Obviously, by doing this you take responsibility that all existing endpoints still function correctly.  And you also
377assume the responsibility of correctly wiring the Transport and Serializer into each endpoint.
378
379
380=== Building the client from a configuration hash
381
382To help ease automated building of the client, all configurations can be provided in a setting
383hash instead of calling the individual methods directly.  This functionality is exposed through
384the `ClientBuilder::FromConfig()` static method, which accepts an array of configurations
385and returns a fully built client.
386
387Array keys correspond to the method name, e.g. `retries` key corresponds to `setRetries()` method.
388
389
390[source,php]
391----
392$params = [
393    'hosts' => [
394        'localhost:9200'
395    ],
396    'retries' => 2,
397    'handler' => ClientBuilder::singleHandler()
398];
399$client = ClientBuilder::fromConfig($params);
400----
401
402
403Unknown parameters will throw an exception, to help the user find potential problems.
404If this behavior is not desired (e.g. you are using the hash for other purposes, and may have
405keys unrelated to the Elasticsearch client), you can set $quiet = true in fromConfig() to
406silence the exceptions.
407
408[source,php]
409----
410$params = [
411    'hosts' => [
412        'localhost:9200'
413    ],
414    'retries' => 2,
415    'imNotReal' => 5
416];
417
418// Set $quiet to true to ignore the unknown `imNotReal` key
419$client = ClientBuilder::fromConfig($params, true);
420----
421