Skip to content

[RFC] Add PSR7 Support, decouple this lib from a http client implementation #455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,25 @@
],
"require": {
"php": "^5.6|^7.0",
"psr/log": "~1.0",
"guzzlehttp/ringphp" : "~1.0"
"psr/log": "^1.0",
"psr/http-message": "^1.0",
"php-http/httplug": "^1.0",
"php-http/logger-plugin": "^1.0",
"php-http/client-common": "dev-master",
"php-http/discovery": "^1.0",
"php-http/message-factory": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^4.7|^5.4",
"mockery/mockery": "0.9.4",
"symfony/yaml": "^2.8",
"symfony/finder": "^2.8",
"cpliakas/git-wrapper": "~1.0",
"sami/sami": "~3.2",
"doctrine/inflector": "^1.1"
"doctrine/inflector": "^1.1",
"monolog/monolog": "^1.21",
"php-http/socket-client": "^1.2",
"php-http/message": "^1.3",
"guzzlehttp/psr7": "^1.3"
},
"suggest": {
"ext-curl": "*",
Expand Down
13 changes: 13 additions & 0 deletions docs/breaking-changes.asciidoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@

== Breaking changes from 2.x

- The client now requires PHP version 5.6 or higher
- The client does not depend on any http client implementation, instead it relies on link:http://httplug.io[Httplug]
which abstract most of existing http client in PHP (guzzle6, guzzle5, react, zend, cakephp, buzz, socket, curl, ...)
- The builder pattern has less options. Please see link:_configuration.html[Configuration] for more details
- Notably setting the handler or ssl is not possible anymore, you should set a `HttpAsyncClient` instead with the
configuration of your choice
- All exceptions about the client (NoNodesAvailable, etc...) do not exist anymore, you should use the one provided by
Httplug
- Many exceptions about Elasticsearch are now under the `Http` namespace, also they include a PSR7 Response and the
original PSR7 Request

== Breaking changes from 1.x

- The client now requires PHP version 5.4 or higher
Expand Down
195 changes: 26 additions & 169 deletions docs/configuration.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -74,47 +74,6 @@ alive nodes, and `setRetries(5)`, the client will attempt to execute the command
result in a connection timeout (for example), the client will throw an `OperationTimeoutException`. Depending on the
Connection Pool being used, these nodes may also be marked dead.

To help in identification, exceptions that are thrown due to max retries will wrap a `MaxRetriesException`. For example,
you can catch a specific curl exception then check if it wraps a MaxRetriesException using `getPrevious()`:

[source,php]
----
$client = Elasticsearch\ClientBuilder::create()
->setHosts(["localhost:1"])
->setRetries(0)
->build();

try {
$client->search($searchParams);
} catch (Elasticsearch\Common\Exceptions\Curl\CouldNotConnectToHost $e) {
$previous = $e->getPrevious();
if ($previous instanceof 'Elasticsearch\Common\Exceptions\MaxRetriesException') {
echo "Max retries!";
}
}
----

Alternatively, all "hard" curl exceptions (`CouldNotConnectToHost`, `CouldNotResolveHostException`, `OperationTimeoutException`)
extend the more general `TransportException`. So you could instead catch the general `TransportException` and then
check it's previous value:

[source,php]
----
$client = Elasticsearch\ClientBuilder::create()
->setHosts(["localhost:1"])
->setRetries(0)
->build();

try {
$client->search($searchParams);
} catch (Elasticsearch\Common\Exceptions\TransportException $e) {
$previous = $e->getPrevious();
if ($previous instanceof 'Elasticsearch\Common\Exceptions\MaxRetriesException') {
echo "Max retries!";
}
}
----


[[enabling_logger]]
=== Enabling the Logger
Expand All @@ -129,8 +88,8 @@ You might have noticed that Monolog was suggested during installation. To begin
{
"require": {
...
"elasticsearch/elasticsearch" : "~2.0",
"monolog/monolog": "~1.0"
"elasticsearch/elasticsearch" : "^2.0",
"monolog/monolog": "^1.0"
}
}
----------------------------
Expand All @@ -142,34 +101,7 @@ And then update your composer installation:
php composer.phar update
----------------------------

Once Monolog (or another logger) is installed, you need to create a log object and inject it into the client. The
`ClientBuilder` object has a helper static function that will generate a common Monolog-based logger for you. All you need
to do is provide the path to your desired logging location:

[source,php]
----
$logger = ClientBuilder::defaultLogger('path/to/your.log');

$client = ClientBuilder::create() // Instantiate a new ClientBuilder
->setLogger($logger) // Set the logger with a default logger
->build(); // Build the client object
----

You can also specify the severity of log messages that you wish to log:

[source,php]
----
// set severity with second parameter
$logger = ClientBuilder::defaultLogger('/path/to/logs/', Logger::INFO);

$client = ClientBuilder::create() // Instantiate a new ClientBuilder
->setLogger($logger) // Set the logger with a default logger
->build(); // Build the client object
----

The `defaultLogger()` method is just a helper, you are not required to use it. You can create your own logger and inject
that instead:

Once Monolog (or another logger) is installed, you need to create a log object and inject it into the client.

[source,php]
----
Expand All @@ -185,69 +117,49 @@ $client = ClientBuilder::create() // Instantiate a new ClientBuilder
----


=== Configure the HTTP Handler
=== Configure the HTTP Client

Elasticsearch-PHP uses an interchangeable HTTP transport layer called https://github.com/guzzle/RingPHP/[RingPHP]. This
allows the client to construct a generic HTTP request, then pass it to the transport layer to execute. The actual execution
details are hidden from the client and modular, so that you can choose from several HTTP handlers depending on your needs.
Elasticsearch-PHP uses an interchangeable HTTP transport layer called http://docs.php-http.org/en/latest/httplug/introduction.html[HTTPlug].
This allows the client to construct a PSR7 HTTP Request, then pass it to the transport layer to execute.
The actual execution details are hidden from the client and modular, so that you can choose from several HTTP clients
depending on your needs.

The default handler that the client uses is a combination handler. When executing in synchronous mode, the handler
uses `CurlHandler`, which executes single curl calls. These are very fast for single requests. When asynchronous (future)
mode is enabled, the handler switches to `CurlMultiHandler`, which uses the curl_multi interface. This involves a bit
more overhead, but allows batches of HTTP requests to be processed in parallel.
By default it will try to find an `HttpAsyncClient` with helps of the http://docs.php-http.org/en/latest/discovery.html[discovery system]

You can configure the HTTP handler with one of several helper functions, or provide your own custom handler:
You can configure the HTTP Client in the builder:

[source,php]
----
$defaultHandler = ClientBuilder::defaultHandler();
$singleHandler = ClientBuilder::singleHandler();
$multiHandler = ClientBuilder::multiHandler();
$customHandler = new MyCustomHandler();

$client = ClientBuilder::create()
->setHandler($defaultHandler)
->build();
----

For details on creating your own custom Ring handler, please see the http://guzzle.readthedocs.org/en/latest/handlers.html[RingPHP Documentation]

The default handler is recommended in almost all cases. This allows fast synchronous execution, while retaining flexibility
to invoke parallel batches with async future mode. You may consider using just the `singleHandler` if you know you will
never need async capabilities, since it will save a small amount of overhead by reducing indirection.

use GuzzleHttp\Client as GuzzleClient;
use Http\Adapter\Guzzle6\Client as GuzzleAdapter;

=== Setting the Connection Pool

The client maintains a pool of connections, with each connection representing a node in your cluster. There are several
connection pool implementations available, and each has slightly different behavior (pinging vs no pinging, etc).
Connection pools are configured via the `setConnectionPool()` method:
$config = [
// Config params of Guzzle6
];

[source,php]
----
$connectionPool = '\Elasticsearch\ConnectionPool\StaticNoPingConnectionPool';
$httpAsyncClient = new GuzzleAdapter(new GuzzleClient($config));
$client = ClientBuilder::create()
->setConnectionPool($connectionPool)
->setHttpAsyncClient($httpAsyncClient)
->build();
----

For more details, please see the dedicated page on link:_connection_pool.html[configuring connection pools].
For details on creating or using an HttpAsyncClient, please see http://docs.php-http.org/en/latest/clients.html[HTTPlug Documentation]

=== Setting the Connection Selector
=== Setting the Client Pool

The connection pool manages the connections to your cluster, but the Selector is the logic that decides which connection
should be used for the next API request. There are several selectors that you can choose from. Selectors can be changed
via the `setSelector()` method:
The connection pool manages the logic that decides which connection should be used for the next API request.
There are several strategies that you can choose from. It can be changed via the `setClientPoolStrategy()` method:

[source,php]
----
$selector = '\Elasticsearch\ConnectionPool\Selectors\StickyRoundRobinSelector';
use Http\Client\Common\HttpClientPool\LeastUsedClientPool;

$client = ClientBuilder::create()
->setSelector($selector)
->setClientPoolStrategy(LeastUsedClientPool::class)
->build();
----

For more details, please see the dedicated page on link:_selectors.html[configuring selectors].
For more details, please see the dedicated page on http://docs.php-http.org/en/latest/components/client-common.html#httpclientpool[HTTPlug].


=== Setting the Serializer
Expand All @@ -269,61 +181,6 @@ $client = ClientBuilder::create()

For more details, please see the dedicated page on link:_serializers.html[configuring serializers].


=== Setting a custom ConnectionFactory

The ConnectionFactory instantiates new Connection objects when requested by the ConnectionPool. A single Connection
represents a single node. Since the client hands actual networking work over to RingPHP, the Connection's main job is
book-keeping: Is this node alive? Did it fail a ping request? What is the host and port?

There is little reason to provide your own ConnectionFactory, but if you need to do so, you need to supply an intact
ConnectionFactory object to the `setConnectionFactory()` method. The object should implement the `ConnectionFactoryInterface`
interface.

[source,php]
----

class MyConnectionFactory implements ConnectionFactoryInterface
{

public function __construct($handler, array $connectionParams,
SerializerInterface $serializer,
LoggerInterface $logger,
LoggerInterface $tracer)
{
// Code here
}


/**
* @param $hostDetails
*
* @return ConnectionInterface
*/
public function create($hostDetails)
{
// Code here...must return a Connection object
}
}


$connectionFactory = new MyConnectionFactory(
$handler,
$connectionParams,
$serializer,
$logger,
$tracer
);

$client = ClientBuilder::create()
->setConnectionFactory($connectionFactory);
->build();
----

As you can see, if you decide to inject your own ConnectionFactory, you take over the responsibiltiy of wiring it correctly.
The ConnectionFactory requires a working HTTP handler, serializer, logger and tracer.


=== Set the Endpoint closure

The client uses an Endpoint closure to dispatch API requests to the correct Endpoint object. A namespace object will
Expand Down Expand Up @@ -378,7 +235,7 @@ $params = [
'localhost:9200'
],
'retries' => 2,
'handler' => ClientBuilder::singleHandler()
'httpAsyncClient' => new GuzzleAdapter(new GuzzleClient([]))
];
$client = ClientBuilder::fromConfig($params);
----
Expand Down
Loading