Hello everyone!

We have been investing plenty of personal time and energy for many years to share our knowledge with you all. However, we now need your help to keep this blog running. All you have to do is just click one of the adverts on the site, otherwise it will sadly be taken down due to hosting etc. costs. Thank you.

In this example we are going to use aggregation feature of elasticsearch. Our example will search students by their names using name field. The "aggregation" section in search result will use awards field in same table.


Database


ID      NAME                  AWARDS
1 John Travolta ["HND","BSc","MSc"]
2 John Bon Jovi ["HND","MBA"]
3 John Legend ["MBA"]
4 John Cena []
5 Robert De Niro ["MBA"]
6 Roberto Di Baggio []
7 Name John ["MSc"]
8 Name John Surname ["MSc"]

Elastica bundle


Setup


You need to install friendsofsymfony/elastica-bundle and enable it in AppKernel.php file.


Config.yml


We will make only name as full-text searchable field because our application will only search by student name.


fos_elastica:
clients:
default: { host: 127.0.0.1, port: 9200 }
indexes:
student_index:
client: default
index_name: student_%kernel.environment%
types:
student:
mappings:
id:
type: integer
index: not_analyzed
name:
type: string
analyzer: english
awards:
type: string
index: not_analyzed
persistence:
driver: orm
model: AppBundle\Entity\Student
finder: ~
provider: ~
listener: ~

Elasticsearch index


Current state


As you can see the index doesn't exists yet.


$ curl 127.0.0.1:9200/_cat/indices?v
health status index pri rep docs.count docs.deleted store.size pri.store.size

Populate


$ bin/console fos:elastica:populate --env=test
Resetting student_index
8/8 [============================] 100%
Populating student_index/student
Refreshing student_index
Refreshing student_index

New state


As you can see the index has been created.


$ curl 127.0.0.1:9200/_cat/indices?v
health status index pri rep docs.count docs.deleted store.size pri.store.size
yellow open student_test 5 1 8 0 16.2kb 16.2kb

Content


$ curl -XGET 127.0.0.1:9200/student_test/_search?pretty=1
{
...
"hits" : {
"total" : 8,
"hits" : [ {
...
"_source":{"id":4,"name":"John Cena","awards":[]}
}, {
...
"_source":{"id":5,"name":"Robert De Niro","awards":["MBA"]}
}, {
...
"_source":{"id":1,"name":"John Travolta","awards":["HND","BSc","MSc"]}
}, {
...
"_source":{"id":6,"name":"Roberto Di Baggio","awards":[]}
}, {
...
"_source":{"id":2,"name":"John Bon Jovi","awards":["HND","MBA"]}
}, {
...
"_source":{"id":7,"name":"Name John","awards":["MSc"]}
}, {
...
"_source":{"id":3,"name":"John Legend","awards":["MBA"]}
}, {
...
"_source":{"id":8,"name":"Name John Surname","awards":["MSc"]}
} ]
}
}

StudentController


namespace AppBundle\Controller;

use AppBundle\Service\StudentService;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Request;

/**
* @Route("/students", service="app.controller.student")
*/
class StudentController
{
private $studentService;

public function __construct(
StudentService $studentService
) {
$this->studentService = $studentService;
}

/**
* @param Request $request
*
* @Method({"GET"})
* @Route("/search")
* @Template
*
* @return array
*/
public function searchAction(Request $request)
{
$name = $request->query->get('name');

return ['result' => $this->studentService->search($name)];
}
}

services:
app.controller.student:
class: AppBundle\Controller\StudentController
arguments:
- "@app.service.student"

StudentService


namespace AppBundle\Service;

use AppBundle\Factory\ModelFactory;
use Elastica\Query;
use Elastica\Type;

class StudentService
{
private $modelFactory;
private $studentType;

public function __construct(
ModelFactory $modelFactory,
Type $studentType
) {
$this->modelFactory = $modelFactory;
$this->studentType = $studentType;
}

public function search($name)
{
$query['query']['match']['name']['query'] = $name;
$query['aggs']['awards']['terms']['field'] = 'awards';

$result = $this->studentType->search(new Query($query));

return $this->modelFactory->createStudentSearchResult($result);
}
}

services:
app.service.student:
class: AppBundle\Service\StudentService
arguments:
- "@app.factory.model"
- "@fos_elastica.index.student_index.student"

ModelFactory


namespace AppBundle\Factory;

use AppBundle\Model\Search\Result as ResultModel;
use Elastica\ResultSet;

class ModelFactory
{
public function createStudentSearchResult(ResultSet $resultSet)
{
$resultModel = new ResultModel();

if ($resultSet->getTotalHits() < 1) {
return $resultModel;
}

$resultModel->total = $resultSet->getTotalHits();
$resultModel->aggregations = array_column($resultSet->getAggregations()['awards']['buckets'], 'doc_count', 'key');

/** @var \Elastica\Result $item */
foreach ($resultSet->getResults() as $item) {
$data = $item->getData();

$studentModel = new StudentModel();
$studentModel->id = $data['id'];
$studentModel->name = $data['name'];
$studentModel->awards = $data['awards'];

$resultModel->students[] = $studentModel;
}

return $resultModel;
}
}

services:
app.factory.model:
class: AppBundle\Factory\ModelFactory

Result


namespace AppBundle\Model\Search;

class Result
{
/**
* @var int
*/
public $total;

/**
* @var array
*/
public $aggregations;

/**
* @var Student[]
*/
public $students;
}

Student


namespace AppBundle\Model\Search;

class Student
{
/**
* @var int
*/
public $id;

/**
* @var string
*/
public $name;

/**
* @var array
*/
public $awards;
}

Search.html.twig


{% extends '::base.html.twig' %}

{% block body %}
{{ dump(result) }}
{% endblock %}

Test


If you go to http://student.dev/app_test.php/students/search?name=john address to search for students named john, the result will look like below. If you compare the data in aggregations section to the actual result, you will see that it is a match.


AppBundle\Model\Search\Result Object
(
[total] => 6
[aggregations] => Array
(
[MSc] => 3
[HND] => 2
[MBA] => 2
[BSc] => 1
)
[students] => Array
(
[0] => AppBundle\Model\Student Object
(
[id] => 1
[name] => John Travolta
[awards] => Array
(
[0] => HND
[1] => BSc
[2] => MSc
)
)
[1] => AppBundle\Model\Student Object
(
[id] => 7
[name] => Name John
[awards] => Array
(
[0] => MSc
)
)
[2] => AppBundle\Model\Student Object
(
[id] => 3
[name] => John Legend
[awards] => Array
(
[0] => MBA
)
)
[3] => AppBundle\Model\Student Object
(
[id] => 2
[name] => John Bon Jovi
[awards] => Array
(
[0] => HND
[1] => MBA
)
)
[4] => AppBundle\Model\Student Object
(
[id] => 8
[name] => Name John Surname
[awards] => Array
(
[0] => MSc
)
)
[5] => AppBundle\Model\Student Object
(
[id] => 4
[name] => John Cena
[awards] => Array
(
)
)
)
)