Skip to content

Commit

Permalink
Psr interfaces (#233)
Browse files Browse the repository at this point in the history
  • Loading branch information
kimpepper authored Jan 7, 2025
1 parent d750838 commit 5855571
Show file tree
Hide file tree
Showing 69 changed files with 1,020 additions and 527 deletions.
1 change: 0 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ jobs:
fail-fast: false
matrix:
php-version:
- '8.0'
- '8.1'
- '8.2'
- '8.3'
Expand Down
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Added samples ([#218](https://github.com/opensearch-project/opensearch-php/pull/218))
- Added support for PHP 8.3 and 8.4 ([#229](https://github.com/opensearch-project/opensearch-php/pull/229))
### Changed
- Switched to PSR Interfaces
- Increased PHP min version to 8.1
- Increased min version of `ezimuel/ringphp` to `^1.2.2`
- Changed fluent setters to return static
### Deprecated
- Passing a callable to \OpenSearch\ClientBuilder::setEndpoint() is deprecated and replaced with passing an EndpointFactory to \OpenSearch\ClientBuilder::setEndpointFactory() ([#237](https://github.com/opensearch-project/opensearch-php/pull/237))
- Connections, Connection pools and Selectors are deprecated. Use a PSR HTTP Client that supports retries instead.
### Removed
- Removed support for PHP 7.3 and 7.4
- Removed support for PHP 7.3, 7.4 and 8.0.
- Removed support for async requests which were never actually working.
### Fixed
- Fixed PHP 8.4 deprecations
### Updated APIs
Expand Down
109 changes: 109 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
- [Upgrading OpenSearch PHP Client](#upgrading-opensearch-php-client)
- [Upgrading to >= 2.0.0](#upgrading-to--240)
- [HTTP Client Auto-Discovery](#http-client-auto-discovery)
- [Configuring Guzzle HTTP Client in 2.x](#configuring-guzzle-http-client-in-2x)
- [Configuring Symfony HTTP Client in 2.x](#configuring-symfony-http-client-in-2x)

# Upgrading OpenSearch PHP Client

## Upgrading to >= 2.4.0

openseach-php removes the hard-coded dependency on the [Guzzle HTTP client](https://docs.guzzlephp.org/en/stable/#) and switches to the following PSR interfaces:

- [PSR-7 HTTP message interfaces](https://www.php-fig.org/psr/psr-7/)
- [PSR-17 HTTP Factories](https://www.php-fig.org/psr/psr-17/)
- [PSR-18 HTTP Client](https://www.php-fig.org/psr/psr-18/)

You can continue to use Guzzle, but will need to configure it as a PSR-18 HTTP Client.

### HTTP Client Auto-Discovery

opensearch-php 2.x will try and discover and install a PSR HTTP Client using [PHP-HTTP Discovery](https://docs.php-http.org/en/latest/discovery.html)
if one is not explicitly provided.

```php
$transport = (new \OpenSearch\TransportFactory())->create();
$endpointFactory = new \OpenSearch\EndpointFactory();
$client = new Client($transport, $endpointFactory, []);

// Send a request to the 'info' endpoint.
$info = $client->info();
```

### Configuring Guzzle HTTP Client in 2.x

To configure Guzzle as a PSR HTTP Client with the similar configuration to opensearch 1.x you can use the following example:

```php
$guzzleClient = new \GuzzleHttp\Client([
'base_uri' => 'https://localhost:9200',
'auth' => ['admin', getenv('OPENSEARCH_PASSWORD')],
'verify' => false,
'retries' => 2,
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'User-Agent' => sprintf('opensearch-php/%s (%s; PHP %s)', \OpenSearch\Client::VERSION, PHP_OS, PHP_VERSION),
]
]);

$guzzleHttpFactory = new \GuzzleHttp\Psr7\HttpFactory();

$serializer = new \OpenSearch\Serializers\SmartSerializer();

$requestFactory = new \OpenSearch\RequestFactory(
$guzzleHttpFactory,
$guzzleHttpFactory,
$guzzleHttpFactory,
$serializer,
);

$transport = (new OpenSearch\TransportFactory())
->setHttpClient($guzzleClient)
->setRequestFactory($requestFactory)
->create();

$endpointFactory = new \OpenSearch\EndpointFactory();
$client = new \OpenSearch\Client($transport, $endpointFactory, []);

// Send a request to the 'info' endpoint.
$info = $client->info();
```

### Configuring Symfony HTTP Client in 2.x

You can configure [Symfony HTTP Client](https://symfony.com/doc/current/http_client.html) as a PSR HTTP Client using
the following example:

```php
$symfonyPsr18Client = (new \Symfony\Component\HttpClient\Psr18Client())->withOptions([
'base_uri' => 'https://localhost:9200',
'auth_basic' => ['admin', getenv('OPENSEARCH_PASSWORD')],
'verify_peer' => false,
'max_retries' => 2,
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
]);

$serializer = new \OpenSearch\Serializers\SmartSerializer();

$requestFactory = new \OpenSearch\RequestFactory(
$symfonyPsr18Client,
$symfonyPsr18Client,
$symfonyPsr18Client,
$serializer,
);

$transport = (new \OpenSearch\TransportFactory())
->setHttpClient($symfonyPsr18Client)
->setRequestFactory($requestFactory)
->create();

$client = new \OpenSearch\Client($transport, $endpointFactory, []);

// Send a request to the 'info' endpoint.
$info = $client->info();

```
31 changes: 22 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,32 @@
}
],
"require": {
"php": "^8.0",
"php": "^8.1",
"ext-json": ">=1.3.7",
"ext-curl": "*",
"ezimuel/ringphp": "^1.2.2",
"psr/log": "^1|^2|^3",
"php-http/discovery": "^1.20",
"psr/http-client": "^1.0",
"psr/http-client-implementation": "*",
"psr/http-factory": "^1.1",
"psr/http-factory-implementation": "*",
"psr/http-message": "^2.0",
"psr/http-message-implementation": "*",
"psr/log": "^2|^3",
"symfony/yaml": "*"
},
"require-dev": {
"ext-zip": "*",
"aws/aws-sdk-php": "^3.0",
"friendsofphp/php-cs-fixer": "^3.0",
"mockery/mockery": "^1.2",
"phpstan/phpstan": "^1.7.15",
"phpstan/phpstan-mockery": "^1.1.0",
"phpunit/phpunit": "^9.3",
"symfony/finder": "~4.0 || ~5.0"
"friendsofphp/php-cs-fixer": "^v3.64",
"guzzlehttp/psr7": "^2.7",
"mockery/mockery": "^1.6",
"phpstan/phpstan": "^1.12",
"phpstan/phpstan-mockery": "^1.1",
"phpunit/phpunit": "^9.6",
"symfony/finder": "^6.4|^7.0",
"symfony/http-client": "^6.4|^7.0",
"symfony/http-client-contracts": "^3.0"
},
"suggest": {
"monolog/monolog": "Allows for client-level logging and tracing",
Expand All @@ -54,7 +64,10 @@
}
},
"config": {
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"php-http/discovery": true
}
},
"scripts": {
"php-cs": [
Expand Down
80 changes: 72 additions & 8 deletions samples/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,79 @@

require_once __DIR__ . '/vendor/autoload.php';

$client = OpenSearch\ClientBuilder::fromConfig([
'Hosts' => [
'https://localhost:9200'
],
'BasicAuthentication' => ['admin', getenv('OPENSEARCH_PASSWORD')],
'Retries' => 2,
'SSLVerification' => false
// Auto-configure by discovery example

$transport = (new \OpenSearch\TransportFactory())->create();
$endpointFactory = new \OpenSearch\EndpointFactory();
$client = new \OpenSearch\Client($transport, $endpointFactory, []);

// Send a request to the 'info' endpoint.
$info = $client->info();

// Guzzle example

$guzzleClient = new \GuzzleHttp\Client([
'base_uri' => 'https://localhost:9200',
'auth' => ['admin', getenv('OPENSEARCH_PASSWORD')],
'verify' => false,
'retries' => 2,
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'User-Agent' => sprintf('opensearch-php/%s (%s; PHP %s)', \OpenSearch\Client::VERSION, PHP_OS, PHP_VERSION),
]
]);

$guzzleHttpFactory = new \GuzzleHttp\Psr7\HttpFactory();

$serializer = new \OpenSearch\Serializers\SmartSerializer();

$requestFactory = new \OpenSearch\RequestFactory(
$guzzleHttpFactory,
$guzzleHttpFactory,
$guzzleHttpFactory,
$serializer,
);

$transport = (new OpenSearch\TransportFactory())
->setHttpClient($guzzleClient)
->setRequestFactory($requestFactory)
->create();

$endpointFactory = new \OpenSearch\EndpointFactory();
$client = new \OpenSearch\Client($transport, $endpointFactory, []);

// Send a request to the 'info' endpoint.
$info = $client->info();

echo "{$info['version']['distribution']}: {$info['version']['number']}\n";
// Symfony example

$symfonyPsr18Client = (new \Symfony\Component\HttpClient\Psr18Client())->withOptions([
'base_uri' => 'https://localhost:9200',
'auth_basic' => ['admin', getenv('OPENSEARCH_PASSWORD')],
'verify_peer' => false,
'max_retries' => 2,
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
]);

$serializer = new \OpenSearch\Serializers\SmartSerializer();

$requestFactory = new \OpenSearch\RequestFactory(
$symfonyPsr18Client,
$symfonyPsr18Client,
$symfonyPsr18Client,
$serializer,
);

$transport = (new \OpenSearch\TransportFactory())
->setHttpClient($symfonyPsr18Client)
->setRequestFactory($requestFactory)
->create();

$client = new \OpenSearch\Client($transport, $endpointFactory, []);

// Send a request to the 'info' endpoint.
$info = $client->info();
29 changes: 29 additions & 0 deletions src/OpenSearch/Aws/SigningClientDecorator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

namespace OpenSearch\Aws;

use Aws\Credentials\CredentialsInterface;
use Aws\Signature\SignatureInterface;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
* A decorator client that signs requests using the provided AWS credentials and signer.
*/
class SigningClientDecorator implements ClientInterface
{
public function __construct(
protected ClientInterface $inner,
protected CredentialsInterface $credentials,
protected SignatureInterface $signer,
) {
}

public function sendRequest(RequestInterface $request): ResponseInterface
{
$request = $request->withHeader('x-amz-content-sha256', hash('sha256', (string) $request->getBody()));
$request = $this->signer->signRequest($request, $this->credentials);
return $this->inner->sendRequest($request);
}
}
41 changes: 26 additions & 15 deletions src/OpenSearch/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ class Client
*/
public $transport;

private TransportInterface $httpTransport;

/**
* @var array
*/
Expand Down Expand Up @@ -263,13 +265,19 @@ class Client
/**
* Client constructor
*
* @param Transport $transport
* @param \OpenSearch\TransportInterface|\OpenSearch\Transport $transport
* @param callable|EndpointFactoryInterface $endpointFactory
* @param NamespaceBuilderInterface[] $registeredNamespaces
*/
public function __construct(Transport $transport, callable|EndpointFactoryInterface $endpointFactory, array $registeredNamespaces)
public function __construct(TransportInterface|Transport $transport, callable|EndpointFactoryInterface $endpointFactory, array $registeredNamespaces)
{
$this->transport = $transport;
if (!$transport instanceof TransportInterface) {
@trigger_error('Passing an instance of \OpenSearch\Transport to ' . __METHOD__ . '() is deprecated in 2.3.2 and will be removed in 3.0.0. Pass an instance of \OpenSearch\TransportInterface instead.', E_USER_DEPRECATED);
$this->transport = $transport;
$this->httpTransport = new LegacyTransportWrapper($transport);
} else {
$this->httpTransport = $transport;
}
if (is_callable($endpointFactory)) {
@trigger_error('Passing a callable as the $endpointFactory param in ' . __METHOD__ . ' is deprecated in 2.3.2 and will be removed in 3.0.0. Pass an instance of \OpenSearch\EndpointFactoryInterface instead.', E_USER_DEPRECATED);
$endpoints = $endpointFactory;
Expand Down Expand Up @@ -2063,33 +2071,36 @@ public function extractArgument(array &$params, string $arg)

/**
* Sends a raw request to the cluster
* @return callable|array
* @throws NoNodesAvailableException
* @return array|string|null
* @throws \Exception
*/
public function request(string $method, string $uri, array $attributes = [])
public function request(string $method, string $uri, array $attributes = []): array|string|null
{
$params = $attributes['params'] ?? [];
$body = $attributes['body'] ?? null;
$options = $attributes['options'] ?? [];

$promise = $this->transport->performRequest($method, $uri, $params, $body, $options);

return $this->transport->resultOrFuture($promise, $options);
return $this->httpTransport->sendRequest($method, $uri, $params, $body, $options['headers'] ?? []);
}

/**
* @return callable|array
* Sends a request for the given endpoint.
*
* @param \OpenSearch\Endpoints\AbstractEndpoint $endpoint
*
* @return array|string|null
*
* @throws \Exception
*/
private function performRequest(AbstractEndpoint $endpoint)
private function performRequest(AbstractEndpoint $endpoint): array|string|null
{
$promise = $this->transport->performRequest(
$options = $endpoint->getOptions();
return $this->httpTransport->sendRequest(
$endpoint->getMethod(),
$endpoint->getURI(),
$endpoint->getParams(),
$endpoint->getBody(),
$endpoint->getOptions()
$options['headers'] ?? []
);

return $this->transport->resultOrFuture($promise, $endpoint->getOptions());
}
}
5 changes: 5 additions & 0 deletions src/OpenSearch/ClientBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
use Psr\Log\NullLogger;
use ReflectionClass;

@trigger_error(ClientBuilder::class . ' is deprecated in 2.3.2 and will be removed in 3.0.0.', E_USER_DEPRECATED);

/**
* @deprecated in 2.3.2 and will be removed in 3.0.0.
*/
class ClientBuilder
{
public const ALLOWED_METHODS_FROM_CONFIG = ['includePortInHostHeader'];
Expand Down
Loading

0 comments on commit 5855571

Please sign in to comment.