Bu örneğimizde elasticsearch aggregation özelliğini kullanacağız. Örneğimizde Product ve Shop tabloları arasında many-to-many ilişki var. Ürünleri name alanına bakarak arayacağız. Arama sonucundaki "aggregation" bölümü, shop tablosundaki name alanını kullanacak.


Veritabanı


ProductID   |ProductName        |ShopID     |ShopName
1 |Product1 |1 |Shop1
1 |Product1 |3 |Shop3
1 |Product1 |4 |Shop4
2 |Product2 |1 |Shop1
2 |Product2 |2 |Shop2
3 |Product3 |1 |Shop1
3 |Product3 |3 |Shop3
4 |Product4 |1 |Shop1
4 |Product4 |2 |Shop2
4 |Product4 |4 |Shop4
5 |Product5 |1 |Shop1
5 |Product5 |3 |Shop3
6 |Product6 |1 |Shop1
6 |Product6 |2 |Shop2
7 |Product Product1 |4 |Shop4

Elastica bundle


Kurulum


Öncelikle composer ile friendsofsymfony/elastica-bundle paketini kurun ve AppKernel.php dosyasında aktifleştirin.


Config.yml


Sadece product.name alanını full-text arama için ayarlıyoruz çünkü uygulmamız sadece o alanı kullanacak.


fos_elastica:
clients:
default: { host: 127.0.0.1, port: 9200 }
indexes:
product_index:
client: default
index_name: product_%kernel.environment%
types:
product:
mappings:
id:
type: integer
index: not_analyzed
name:
type: string
analyzer: english
shops:
type: object
properties:
id:
type: integer
index: not_analyzed
name:
type: string
index: not_analyzed
persistence:
driver: orm
model: AppBundle\Entity\Product
finder: ~
provider: ~
listener: ~

Elasticsearch index


Mevcut durum


Aşağıda da gördüğümüz gibi index henüz mevcut değil.


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

Doldurma işlemi


$ bin/console fos:elastica:populate --env=test
Resetting product_index
7/7 [============================] 100%
Populating product_index/product
Refreshing product_index
Refreshing product_index

Yeni durum


Aşağıda da gördüğümüz gibi index yaratılmış durumda.


$ 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 product_test 5 1 7 0 16.2kb 16.2kb

İçerik


$ curl -XGET 127.0.0.1:9200/product_test/_search?pretty=1
{
...
"hits" : {
"total" : 7,
"hits" : [ {
...
"_source":{"id":4,"name":"Product4","shops":[{"id":1,"name":"Shop1"},{"id":2,"name":"Shop2"},{"id":4,"name":"Shop4"}]}
}, {
"_source":{"id":5,"name":"Product5","shops":[{"id":1,"name":"Shop1"},{"id":3,"name":"Shop3"}]}
}, {
"_source":{"id":1,"name":"Product1","shops":[{"id":1,"name":"Shop1"},{"id":3,"name":"Shop3"},{"id":4,"name":"Shop4"}]}
}, {
"_source":{"id":6,"name":"Product6","shops":[{"id":1,"name":"Shop1"},{"id":2,"name":"Shop2"}]}
}, {
"_source":{"id":2,"name":"Product2","shops":[{"id":1,"name":"Shop1"},{"id":2,"name":"Shop2"}]}
}, {
"_source":{"id":7,"name":"Product Product1","shops":[{"id":4,"name":"Shop4"}]}
}, {
"_source":{"id":3,"name":"Product3","shops":[{"id":1,"name":"Shop1"},{"id":3,"name":"Shop3"}]}
} ]
}
}

ProductController


namespace AppBundle\Controller;

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

/**
* @Route("/products", service="app.controller.product")
*/
class ProductController
{
private $productService;

public function __construct(
ProductService $productService
) {
$this->productService = $productService;
}

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

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

services:
app.controller.product:
class: AppBundle\Controller\ProductController
arguments:
- "@app.service.product"

ProductService


namespace AppBundle\Service;

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

class ProductService
{
private $modelFactory;
private $productType;

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

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

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

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

services:
app.service.product:
class: AppBundle\Service\ProductService
arguments:
- "@app.factory.model"
- "@fos_elastica.index.product_index.product"

ModelFactory


namespace AppBundle\Factory;

use AppBundle\Model\Product\Search\Result as ProductSearchResultModel;
use AppBundle\Model\Product\Search\Product as ProductSearchModel;
use AppBundle\Model\Product\Shop as ShopSearchModel;
use Elastica\ResultSet;

class ModelFactory
{
public function createProductSearchResult(ResultSet $resultSet)
{
$resultModel = new ProductSearchResultModel();

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

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

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

$productSearchModel = new ProductSearchModel();
$productSearchModel->id = $data['id'];
$productSearchModel->name = $data['name'];

foreach ($data['shops'] as $shop) {
$shopSearchModel = new ShopSearchModel();
$shopSearchModel->id = $shop['id'];
$shopSearchModel->name = $shop['name'];

$productSearchModel->shops[] = $shopSearchModel;
}

$resultModel->products[] = $productSearchModel;
}

return $resultModel;
}
}

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

Result


namespace AppBundle\Model\Product\Search;

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

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

/**
* @var Product[]
*/
public $products;
}

Product


namespace AppBundle\Model\Product\Search;

use AppBundle\Model\Product\Shop;

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

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

/**
* @var Shop[]
*/
public $shops;
}

Shop


namespace AppBundle\Model\Product;

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

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

Search.html.twig


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

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

Test


Eğer http://product.dev/app_test.php/products/search?name=Product1 adresine Product1 ismindeki ürünleri aramak için giderseniz, aşağıdakine benzer bir sonuç alırsınız. Eğer aggregations bölümündeki veriyi, asıl sonuç ile karşılaştırırsanız, sonuçların uyduğunu göreceksiniz.


AppBundle\Model\Product\Search\Result Object
(
[total] => 2
[aggregations] => Array
(
[Shop4] => 2
[Shop1] => 1
[Shop3] => 1
)
[products] => Array
(
[0] => AppBundle\Model\Product\Search\Product Object
(
[id] => 1
[name] => Product1
[shops] => Array
(
[0] => AppBundle\Model\Product\Shop Object
(
[id] => 1
[name] => Shop1
)
[1] => AppBundle\Model\Product\Shop Object
(
[id] => 3
[name] => Shop3
)
[2] => AppBundle\Model\Product\Shop Object
(
[id] => 4
[name] => Shop4
)
)
)
[1] => AppBundle\Model\Product\Search\Product Object
(
[id] => 7
[name] => Product Product1
[shops] => Array
(
[0] => AppBundle\Model\Product\Shop Object
(
[id] => 4
[name] => Shop4
)
)
)
)
)