20/05/2017 - ELASTICSEARCH, SYMFONY
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.
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"]
You need to install friendsofsymfony/elastica-bundle
and enable it in AppKernel.php file.
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: ~
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
$ bin/console fos:elastica:populate --env=test
Resetting student_index
8/8 [============================] 100%
Populating student_index/student
Refreshing student_index
Refreshing student_index
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
$ 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"]}
} ]
}
}
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"
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"
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
namespace AppBundle\Model\Search;
class Result
{
/**
* @var int
*/
public $total;
/**
* @var array
*/
public $aggregations;
/**
* @var Student[]
*/
public $students;
}
namespace AppBundle\Model\Search;
class Student
{
/**
* @var int
*/
public $id;
/**
* @var string
*/
public $name;
/**
* @var array
*/
public $awards;
}
{% extends '::base.html.twig' %}
{% block body %}
{{ dump(result) }}
{% endblock %}
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
(
)
)
)
)