Apresentamos o novo cliente PHP para o Elasticsearch 8
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.
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:
- Um cliente PSR-18, usando ClientInterface
- Um pool de nós, usando NodePoolInterface
- Um logger PSR-3, usando LoggerInterface
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!