Back-End

9 out, 2014

ElasticSearch com PHP não é magia; é tecnologia!

Publicidade

Já não é de hoje que a ciência e a tecnologia desmistificaram a magia, então vamos entender um pouco o nosso assunto.

A alguns anos atrás começaram a aparecer em diversos sites grandes a famosa “busca viva”. De início, parecia ser algo mágico, mas depois foi descoberto que na maioria das vezes o cara por trás disso era o nosso querido ElasticSearch. Ele nada mais é do que um servidor de buscas escrito em Java e baseado no Apache Lucene. Hoje ele e usado por empresas como GitHub, Facebook, TheGuardian, Globo.com e outras…

Quando pensamos no uso do ElasticSearch com PHP não pensamos na busca viva, pois essa fica por conta do Javascript. No PHP nós trabalhamos com a indexação de dados. Uma vez que o ElasticSearch basicamente é um banco de dados, então nossos dados de busca precisam ser indexados nele de alguma maneira, e a maneira mais fácil de fazer isso é com um backend que “escute” suas ações e salve as no ElasticSearch.

Vamos usar o client oficial do ElasticSearch para PHP, o elasticsearch-php. Ele é bem completo, mas tem uma documentação bem incompleta (coisa comum no mundo de hoje).

Para nosso artigo, eu criei uma Factory simples e também um exemplo. Vamos por a mão na massa então.

Instalando o ElasticSearch

A primeira coisa a fazer e instalar o ElasticSearch. Depois, o elasticsearch-php usando o composer. Aqui esta a dependência:

{
    "require": {
        "elasticsearch/elasticsearch": "~1.0"
    }
}

Após instalado, podem criar uma factory como a minha que está aqui.

Vamos entender essa Factory, então… Basicamente ela vai criar uma instância do elasticsearch client php para que possamos indexar, buscar e fazer update nos documents dentro do elasticsearch.

O elasticsearch se divide em index (índice), type (tipo) e depois document. O índice é usado, por exemplo, como o nome do meu banco de dados. Já o tipo seria minha tabela e o document são nossos dados.

Bom, a nossa factory se inicia com métodos privados que vamos usar logo e também um construtor na linha :16.

public function __construct($index){
		$this->index = $index;
		$this->client = new Elasticsearch\Client();

		return $this;
	}

Nosso construtor recebe um index obrigatoriamente, pois independente da nossa ação precisamos de um índice setado. Ela também inicia uma instância do nosso elasticsearch client. Também nessa factory, temos os getters e os setters para acessarmos nossas variáveis privadas.

Bom, então nos já temos o construtor, os getters e os setters. Agora precisamos criar os métodos para indexar e buscar dados do nosso elasticsearch.

Na linha :25 temos o método create:

public function create()
	{
		$params['index'] = $this->index;
		$params['type'] = $this->type;
		$params['body'] = $this->data;
		
		return $this->client->index($params);
	}

Nosso método create tem como um índice um tipo e um body. Com esses dados setados, nós vamos chamar o index do elasticsearch cliente e passar esses parâmetros pra ele indexar – vejam que eu não passei um “id”, então ele vai criar um id automaticamente para nós.

Na pratica ficaria assim então:

$ef = new ElasticFactory('my_project');
$ef->setType('users');

$data = [
	'name'=>'Waldemar Neto',
	'age'=>24,
	'email'=>'waldemarnt@outlook.com',
	'born'=>'1990/01/23'
];

$ef->setData($data);
$ef->create();

Pronto! Populamos nossos dados e mandamos salvar (notem uma coisa: nosso campo ‘born” e uma data está no padrão yyyy/mm/dd, que o elasticsearch mapeara o tipo dele como date e poderemos usar filtros de data nele no futuro.

Agora na linha :35 eu tenho o método update que é semelhante ao create, porém tem o id e o [‘body’][‘doc’]:

public function update()
	{
		$params['index'] = $this->index;
		$params['type'] = $this->type;
		$params['body'][‘doc’] = $this->data;
		$params['id'] = $this->id;

		return $this->client->index($params);
	}

Aqui faríamos o mesmo que acima, sá que setariámos o id usando o $ef->setId(<id>). É comum setarmos o id, pois em projetos normais indexamos dados que já usamos no banco de dados, por exemplo, e eles já têm um id.

Queries

Agora vamos ver como funcionam as buscas. Na linha :46 da minha factory criei o seguinte método:

public function find($query)
	{	
		if($this->type){
			$params['type'] = $this->type;
		}

		$params['body']['query'] = $query;
		$params['size'] = $this->size;

		return $this->client->search($params);
	}

Esse método vai receber uma query e vai chamar o search do elasticsearch client passando os parâmetros da busca.

As queries do elasticsearch normal são com objetos json. A nossa, aqui no php é com arrays mesmo; depois o client transforma essa request em json.

Então vamos fazer uma Match Query buscando por idade:

$ef = new ElasticFactory('my_project');
$ef->setType('users');
$ef->setSize(100);
$matchQuery=[
	'match'=> [
		'age'=>24
	]
];

$matchData = $ef->find($matchQuery);
if(isset($matchData['hits']['hits'])){
	foreach ($matchData['hits']['hits'] as $key => $hit) {
		echo $hit['_source']['name'].'<br/>';
	}
}

Vamos entender essa query agora. Primeiro, após instanciar eu setei o size como 100 (pois por padrao, o elasticsearch trabalha com size 10, ou seja mesmo que ele encontre 1000 retornos, ele retornara 10). Depois eu criei um array passando o nome da query que é match (podemos encontrar informações sobre queries na documentação do elasticsearch). Depois eu chamei o meu método find e passei pra ele nosso array com os parâmetros da busca. Agora é so testar o retorno para ver se ele achou algo.

Por padrão, ele sempre nos retornara um array hits com outro array hits dentro e com outro array chamado _source. Então, pra validar se retornou, usamos isset($retorno[‘hits’][‘hits’][‘_source’]) se der true é porque ele nos retornou dados.

Agora vamos ver buscas mais complexas:

$filter = array();
$filter['term']['name'] = 'waldemar';

$query = array();
$query['match']['age'] = 24;

$filteredQuery = [
	'filtered'=>[
		'filter'=>$filter,
		'query'=>$query
	]
];


$filteredData = $ef->find($filteredQuery);

if(isset($filteredData['hits']['hits'])){
	foreach ($filteredData['hits']['hits'] as $key => $hit) {
		echo $hit['_source']['name'].'<br/>';
	}
}

Essa será uma filtered query, que é quem entende que para fazer a query, ela tem que atender aos filters que ela possui. Então, primeiro criei um array $filter e usei um term filter (também disponível na documentação do elasticsearch) o meu filter é por term e o campo que eu quero é o nome que possua “waldemar” como valor. A minha query vai ser uma query do tipo match que eu quero trazer as idades(age) de valor 24.

Beleza com esses dois arrays agora eu crio o array da filtered query e passo pra ele o meu array de filter com meu filtro e na query passo o array da query. Pronto! Agora e só passar isso para o find e ver se nos retornou dados.

Agora vamos um pouco mais além nas queries e vamos usar um filter do tipo range, que e muito usado em datas ou em escalas de distância, por exemplo:

$filter = array();
$filter['range']['born'] =[
	'gte'=>'1990/01/20',
	'lte'=>'1990/01/24'
];

$query = array();
$query['match_all'][] = [];

$filteredByBorn = [
	'filtered'=>[
		'filter'=>$filter,
		'query'=>$query
	]
];


$filteredBorn = $ef->find($filteredByBorn);

if(isset($filteredBorn['hits']['hits'])){
	foreach ($filteredBorn['hits']['hits'] as $key => $hit) {
		echo $hit['_source']['name'].'<br/>';
	}
}

A nossa query dessa vez será uma match_all, que traz todos os dados, e vamos filtrar pelas datas usando o range filter, lembrando que para funcionar o range filter o campo no qual estamos buscando precisa ser do tipo date.

Bom galera, espero que tenham gostado. É um exemplo bem básico, mas já dá pra ter uma ideia de por onde começar com essa fantástica ferramenta que é o elasticsearch, que está ganhando o mercado.