Apresentamos o novo cliente PHP para o Elasticsearch 8

library-branding-elastic-enterprise-search-midnight-1680x980-no-logo.png

O novo cliente PHP para Elasticsearch 8 foi reescrito do zero. Além de adotar os padrões PSR, também redesenhamos a arquitetura e movemos a camada de transporte HTTP para fora. Um sistema conectável também está disponível, graças à biblioteca HTTPlug.

Continue lendo para explorar:

  • A nova arquitetura e os recursos do cliente PHP
  • Como consumir os endpoints e gerenciar erros usando o padrão PSR-7 para mensagens HTTP
  • Como interagir com o Elasticsearch usando uma abordagem assíncrona

O cliente antigo

A biblioteca elasticsearch-php é o cliente oficial para programar o Elasticsearch com PHP. Essa biblioteca expõe todos os mais de 400 endpoints do Elasticsearch usando uma classe Client principal. Na versão 7 dessa biblioteca, todos os endpoints são expostos usando funções — por exemplo, a API de índice é mapeada no método Client::index().

Essas funções retornam uma array associativa que é a desserialização da resposta HTTP do Elasticsearch. Normalmente, essa resposta é representada por uma mensagem JSON. Essa mensagem é convertida em uma array usando a função json_decode() do PHP.

Em caso de erros, o cliente lança uma exceção que depende do problema. Por exemplo, se a resposta HTTP for 404, o cliente lançará uma Missing404Exception. Se quiser recuperar a resposta HTTP em si, você precisará obter a última resposta do cliente usando o seguinte código:

$response = $client->info();
$last = $client->transport->getLastConnection()->getLastRequestInfo();
 
$request = $last['request']; // associative array of the HTTP request
var_dump($request);
 
$response = $last['response']; // associative array of the HTTP response
echo $response['status']; // 200
echo $response['body'];   // the body as string

A solicitação e a resposta HTTP são recuperadas da camada de transporte (uma propriedade do Client) com alguns métodos: getLastConnection() e getLastRequestInfo().

Esse código não oferece uma boa experiência ao desenvolvedor porque as chaves da array associativa $response são muitas, provenientes do uso de extensões cURL do PHP.

O novo cliente

Construímos o novo elasticsearch-php 8 do zero por vários motivos: experiência do desenvolvedor, novos padrões PHP, arquitetura mais aberta e desempenho.

Com cerca de 70 milhões de instalações, não queríamos ter muitas quebras de compatibilidade binária para a versão 8. Usamos uma abordagem de compatibilidade com versões anteriores oferecendo as mesmas APIs da versão 7. Isso significa que você pode se conectar ao Elasticsearch usando o mesmo código e executar uma chamada de endpoint normalmente. A diferença está na resposta. Na versão 8, a resposta é um objeto de resposta do Elasticsearch que implementa a interface de resposta PSR-7 e a interface ArrayAccess do PHP.

Espere aí — isso não é uma grande quebra de compatibilidade binária? Felizmente, implementamos a interface ArrayAccess, e você pode continuar consumindo a resposta como uma array, da seguinte forma:

Descubra as diferenças: o espaço de nome foi alterado! Introduzimos o espaço de nome raiz Elastic. O outro código parece igual, mas há uma grande mudança nos bastidores.

Como mencionamos, $response na versão 8 é um objeto, enquanto na versão 7 é uma array associativa. Se você deseja exatamente o mesmo comportamento da versão 7, pode serializar a resposta como uma array usando a função $response->asArray().

Também oferecemos as funções asObject(), asString() e asBool() para serializar o corpo como um objeto da classe padrão do PHP (stdClass), como string ou booliano (verdadeiro se a resposta for 2xx, caso contrário, falso).

Por exemplo, você pode consumir o endpoint info() anterior da seguinte forma:

$client = ClientBuilder::create()
   ->setHosts(['localhost:9200'])
   ->build();
$response = $client->info();
 
echo $response['version']['number'];
echo $response->version->number; // 8.0.0
 
var_dump($response->asObject()); // response body content as stdClass object
var_dump($response->asString()); // response body as string (JSON)
var_dump($response->asBool());   // true if HTTP response code 2xx

O $response é capaz de acessar o corpo da resposta como um objeto que implementa o método mágico _get() do PHP.

Se quiser ler a resposta HTTP, não será necessário recuperar a última mensagem do objeto Client; você pode simplesmente acessar a mensagem PSR-7 no próprio $response , da seguinte forma:

echo $response->getStatusCode();    // 200, since $response is PSR-7
echo (string) $response->getBody(); // Response body in JSON

Essa é uma grande vantagem, especialmente se você estiver trabalhando com uma abordagem assíncrona. Na verdade, você não poderá recuperar a última resposta do cliente se estiver usando programação assíncrona. Não há garantia de que a última resposta seja a que você está procurando (veja mais informações sobre operações assíncronas posteriormente neste artigo).

Parâmetros de endpoint para preenchimento automático

Adicionamos uma funcionalidade de preenchimento automático no elasticsearch-php versão 8 usando as arrays semelhantes a objetos do projeto Psalm. O Psalm é uma ferramenta de análise estática que permite aos desenvolvedores decorar o código usando o atributo especial phpDoc. Um desses atributos é @psalm-type, que permite especificar os tipos de chave de uma array associativa. Aplicamos o tipo do Psalm usando o phpDoc padrão @param. Cada endpoint do cliente PHP tem um parâmetro de entrada que é a array $params. Por exemplo, aqui é relatada a seção do endpoint index():

/**
    * Creates or updates a document in an index.
    *
    * @see https://www.elastic.co/guide/en/elasticsearch/reference/master/docs-index_.html
    *
    * @param array{
    *     id: string, //  Document ID
    *     index: string, // (REQUIRED) The name of the index
    *     wait_for_active_shards: string, // Sets the number of shard copies …
    *     op_type: enum, // Explicit operation type. Defaults to `index` for requests…
    *     refresh: enum, // If `true` then refresh the affected shards to make this operation…
    *     routing: string, // Specific routing value
    *     timeout: time, // Explicit operation timeout
    *     version: number, // Explicit version number for concurrency control
    *     version_type: enum, // Specific version type
    *     if_seq_no: number, // only perform the index operation if the last operation…
    *     if_primary_term: number, // only perform the index operation if the last operation…
    *     pipeline: string, // The pipeline id to preprocess incoming documents with
    *     require_alias: boolean, // When true, requires destination to be an alias…
    *     pretty: boolean, // Pretty format the returned JSON response. (DEFAULT: false)
    *     human: boolean, // Return human readable values for statistics. (DEFAULT: true)
    *     error_trace: boolean, // Include the stack trace of returned errors. (DEFAULT: false)
    *     source: string, // The URL-encoded request definition. Useful for libraries…
    *     filter_path: list, // A comma-separated list of filters used to reduce the response.
    *     body: array, // (REQUIRED) The document
    * } $params
    */
   public function index(array $params = [])

Todos os parâmetros são especificados com um nome (índice), incluindo o tipo (string) e um comentário que descreve o parâmetro (o nome do índice). Os parâmetros necessários são especificados usando uma nota REQUIRED.

Você pode ter o preenchimento automático em seu IDE usando a notação anterior. Por exemplo, usando PhpStorm, você pode instalar o plugin gratuito deep-assoc-completion para habilitar o preenchimento automático da array associativa do PHP com o atributo @psalm-type.

Video thumbnail

O deep-assoc-completion também está disponível para o Visual Studio Code, mesmo que esta versão ainda esteja em desenvolvimento.

Arquitetura conectável

Outra mudança que fizemos na versão 8 foi a separação entre a camada de transporte HTTP e aa biblioteca. Criamos a biblioteca elastic-transport-php que é um cliente PSR-18 para conexão com os produtos da Elastic no PHP. Essa biblioteca é consumida não apenas pelo elasticsearch-php, mas também por outros projetos como o enterprise-search-php.

Essa biblioteca é baseada em uma arquitetura conectável, o que significa que você pode configurá-la para usar uma implementação específica das seguintes interfaces:

O elasticsearch-php versão 8 usa o elastic-transport-php como dependência. Isso significa que você pode se conectar ao Elasticsearch usando uma biblioteca HTTP customizada, um pool de nós customizado ou um logger customizado.

Usamos a biblioteca HTTPlug para realizar uma descoberta automática das bibliotecas PSR-18 e PSR-7 disponíveis em uma aplicação PHP. Por padrão, se a aplicação não tem uma biblioteca HTTP instalada, usamos o Guzzle.

Por exemplo, você pode usar o cliente HTTP da Symfony da seguinte forma:

use Symfony\Component\HttpClient\Psr18Client;
 
$client = ClientBuilder::create()
   ->setHttpClient(new Psr18Client)
   ->build();

Alternativamente, você pode usar a biblioteca do logger Monolog da seguinte forma:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;
 
$logger = new Logger('name');
$logger->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
$client = ClientBuilder::create()
   ->setLogger($logger)
   ->build();

Para obter mais informações sobre como personalizar o Client, consulte a página Configuration (Configuração).

Conecte-se ao Elastic Cloud

O Elastic Cloud é a solução de PaaS oferecida pela Elastic. Para se conectar ao Elastic Cloud, você só precisa do Cloud ID (ID da nuvem) e da chave de API.

O Cloud ID pode ser obtido na página My deployment (Minha implantação) do dashboard do Elastic Cloud. A chave de API pode ser gerada na seção Management (Gerenciamento) nas configurações da página Security (Segurança).

Você pode ler a seção Connecting (Conexão) da documentação do cliente PHP para obter mais informações.

Depois de coletar o Cloud ID e a chave de API, você pode usar o elasticsearch-php para se conectar à sua instância do Elastic Cloud, da seguinte forma:

$client = ClientBuilder::create()
   ->setElasticCloudId('insert here the Cloud ID')
   ->setApiKey('insert here the API key')
   ->build();

Segurança por padrão

Se você instalou o Elasticsearch 8 na sua infraestrutura, poderá usar o cliente PHP com o TLS (segurança da camada de transporte) habilitado. O Elasticsearch 8 oferece segurança por padrão, ou seja, ele usa o TLS para proteger a comunicação entre o cliente e o servidor.

Para configurar o elasticsearch-php para se conectar ao Elasticsearch 8, você precisa ter o arquivo de autoridade de certificação (CA).

Você pode instalar o Elasticsearch de diferentes maneiras. Se você usa o Docker, precisa executar o seguinte comando:

docker pull docker.elastic.co/elasticsearch/elasticsearch:8.1.0

Depois de instalar a imagem do Docker, você pode executar o Elasticsearch usando uma configuração de cluster de nó único, da seguinte forma:

docker network create elastic
docker run --name es01 --net elastic -p 9200:9200 -p 9300:9300 -it docker.elastic.co/elasticsearch/elasticsearch:8.1.0

Esse comando cria uma rede elastic Docker e inicia o Elasticsearch usando a porta 9200 (padrão).

Quando você executa a imagem do Docker, uma senha é gerada para o usuário do Elastic e impressa no terminal (talvez seja necessário voltar um pouco rolando a tela no terminal para visualizá-la). Você precisa copiá-la para se conectar ao Elasticsearch.

Agora que o Elasticsearch está em execução, podemos obter o certificado do arquivo http_ca.crt. Copie-o da instância do Docker usando o seguinte comando:

docker cp es01:/usr/share/elasticsearch/config/certs/http_ca.crt .

Assim que tivermos o certificado http_ca.crt e a senha, copiados durante o início do Elasticsearch, podemos usá-los para nos conectarmos da seguinte forma:

$client = ClientBuilder::create()
   ->setHosts(['https://localhost:9200'])
   ->setBasicAuthentication('elastic', 'password copied during ES start')
   ->setCABundle('path/to/http_ca.crt')
   ->build();

Usar o cliente em modo assíncrono

O cliente PHP ofereceu a possibilidade de executar chamadas assíncronas para cada endpoint. Com a versão 7, você precisava especificar um valor future => lazy especial na chave client passada como parâmetro para o endpoint, da seguinte forma:

$params = [
   'index' => 'my-index',
   'client' => [
      'future' => 'lazy'
   ],
   'body' => [
       'foo' => 'bar'
   ]
];
$response = $client->index($params);

O exemplo anterior indexa o documento { "foo": "bar" } no Elasticsearch usando uma chamada HTTP assíncrona. O $response é um futuro, e não a resposta real.

Um futuro representa um cálculo futuro e atua como um espaço reservado. Você pode passar um futuro em torno do seu código como um objeto normal. Quando precisar dos valores dos resultados, você poderá resolver o futuro. Caso o futuro já tenha sido resolvido (devido a alguma outra atividade), os valores ficarão imediatamente disponíveis. Se o futuro ainda não tiver sido resolvido, a resolução será bloqueada até que esses valores fiquem disponíveis (por exemplo, após a conclusão da chamada de API).

Na versão 7, o futuro é na verdade um Promise do projeto RingPHP. Na versão 8, caso você queira utilizar a abordagem assíncrona, será necessário instalar o adaptador específico para seu cliente HTTP. Por exemplo, se você estiver usando o Guzzle 7 (a biblioteca HTTP padrão para o elasticsearch-php), será necessário instalar o php-http/guzzle7-adapter da seguinte forma:

composer require php-http/guzzle7-adapter

Para executar um endpoint usando uma chamada assíncrona, você precisa habilitá-lo usando a função Client::setAsync(true), da seguinte forma:

$client->setAsync(true);
$params = [
   'index' => 'my-index',
   'body' => [
       'foo' => 'bar'
   ]
];
$response = $client->index($params);

Se quiser desabilitar a chamada assíncrona para o próximo endpoint, você precisará definir setAsync como false novamente.

A resposta de uma chamada assíncrona é um objeto Promise da biblioteca HTTPlug. O Promise segue o padrão Promises/A+. Uma promessa representa o resultado final de uma operação assíncrona.

Para obter a resposta, você precisa esperar que ela chegue. Isso bloqueará a execução que estiver aguardando a resposta da seguinte forma:

$response = $client->index($params);
$response = $response->wait();
printf("Body response:\n%s\n", $response->asString());

A principal forma de interagir com uma promessa é pelo método then, que registra retornos de chamada para receber o valor final de uma promessa ou o motivo pelo qual a promessa não pode ser cumprida.

$response = $client->index($params);
$response->then(
   // The success callback
   function (ResponseInterface $response) {
       // $response is Elastic\Elasticsearch\Response\Elasticsearch
       printf("Body response:\n%s\n", $response->asString());
   },
   // The failure callback
   function (\Exception $exception) {
       echo 'Houston, we have a problem';
       throw $exception;
   }
);
$response = $response->wait();

O último $response->wait() é necessário para resolver a chamada de execução no exemplo acima.

Menos código e uso de memória

O novo cliente PHP para o Elasticsearch usa menos código em comparação com a versão 7. Em particular, o elasticsearch-php versão 8 é composto por 6.522 linhas de código + 1.025 linhas de código do elastic-transport-php, perfazendo um total de 7.547 linhas. Na versão 7, tínhamos 20.715 linhas de código, portanto, a versão 8 tem cerca de um terço do tamanho da anterior.

Em relação ao uso de memória, o elasticsearch-php versão 8 implementa um mecanismo de carregamento lento para otimizar o carregamento do espaço de nome da API. Isso significa que, se você estiver usando apenas um subconjunto dos mais de 400 endpoints, não carregará na memória todas as especificações.

Conclusão

O Elasticsearch 8 vem com algumas melhorias interessantes. Desde a nova arquitetura e a capacidade de preservar a compatibilidade retroativa com a versão 7 até as configurações de segurança padrão e as vantagens do modo assíncrono, as possibilidades nunca foram tantas.

A melhor maneira de começar é com o Elastic Cloud. Inicie sua avaliação gratuita do Elastic Cloud hoje mesmo!