Is Elasticsearch really for you?
\file_get_contents()
\json_decode()
$results = json_decode(
file_get_contents('http://localhost:9200/_search')
);
We can do better!
elasticsearch/elasticsearch
= Officialruflin/elastica
friendsofsymfony/elastica-bundle
madewithlove/elasticsearcher
(5.x)ongr/elasticsearch-dsl
doctrine/search
Surprise! π€ͺLow level, knows all the API, associative array:
$params = [
'index' => 'app',
'body' => [
'query' => [
'bool' => [
'must' => [
'match' => [ 'framework' => 'symfony' ]
]
]
]
]
];
$response = $client->search($params);
Object oriented, based on the official client
$bool = new BoolQuery();
$bool->addMust(
new Match('framework', 'symfony')
);
$response = $client->search($bool);
Bridge between Doctrine and Elastica
fos_elastica:
indexes:
app:
persistence:
driver: orm
model: App\Entity\Product
$ bin/console fos:elastica:populate
Library | Pros | Cons |
---|---|---|
Official Client |
|
|
Elastica |
|
|
FOSElasticaBundle |
|
|
$index = $client->getIndex('app');
$id = '42';
$content = ['name' => 'hans', 'likes' => ['2', '3']];
$doc = new Document($id, $content);
$index->addDocuments([$doc]); // Send a Bulk
If you're still using nested associative arrays for that, You're Doing It Wrong(tm).Use associative arrays basically never / Never type hint on arrays
$index = $client->getIndex('app');
$id = '42';
-$content = ['name' => 'hans', 'likes' => ['2', '3']];
+$content = '{"name": "hans", "likes": ["2", "3"]}';
$doc = new Document($id, $content);
$index->addDocuments([$doc]); // Send a Bulk
$user = new User();
$user->name = 'hans';
$user->likes = ['2', '3'];
ππ½ from and to ππ½
{"name": "hans", "likes": ["2", "3"]}
symfony/serializer
JMSSerializer
Jane
...We are also going to deserialize for our search result!
$serializer = $container->get('serializer');
$user = new User();
$user->name = 'hans';
$user->likes = ['2', '3'];
$doc = new Document(
43,
$serializer->serialize($user, 'json')
);
$index->addDocuments([$doc]);
Jane allow to generate the perfect plain PHP Normalizer via a JSON Schema or AutoMapper!
$results = $index->search(
new \Elastica\Query\Match('name', 'washwash')
);
Elastica return a Elastica\Result
,
where "data" is an associative array.
Elastica\ResultSet:
results: Elastica\Result[]
Implement your own builder.
$result = new Result($hit);
$result->setModel(
$this->serializer->denormalize(
$result->getSource(),
\User::class
);
);
Pass it to searches:
use \JoliCode\Elastically\ResultSetBuilder;
$search = $index->createSearch(
$query,
null,
new ResultSetBuilder($serializer)
);
PUT /app
{
"settings": {
"number_of_shards": 1,
"analysis": {
"analyzer": {
"yolo": {
"tokenizer": "standard"
}
}
}
},
"mappings": {
"properties": {
"name": { "type": "text", "analyzer": "yolo" },
"ref": { "type": "text", "analyzer": "yolo" }
}
}
}
βοΈ οΈLots of repetition
filter:
app_french_stemmer:
type: stemmer
language: light_french
analyzer:
app_french_heavy:
tokenizer: icu_tokenizer
filter:
- app_french_elision
- icu_folding
- app_french_stemmer
settings:
number_of_shards: 1
# Include analyzers.yaml here
mappings:
properties:
name: &txt_basic
type: text
analyzer: app_standard
ref: *txt_basic
app_2020-12-001
app_2020-12-002
with alias app_search
app_2020-12-003
Don't use dynamic mapping unless you like random results
PUT /app/_doc/1
{ "rating": 9 }
PUT /app/_doc/2
{ "rating": 9.9 }
GET /app/_mapping
> { "rating": { "type": "long" }}
Oops, 9.9 is stored as 9 with no warning!
Disable it and sleep better.
mappings:
dynamic: false
properties:
rating:
...
{
title: "SymfonyWorld Online 2020",
url: "https://live.symfony.com/2020-world/"
}
We do not need to index "url".
Minimal message, let the worker do the job
namespace JoliCode\Elastically\Messenger;
final class IndexationRequest
{
private $operation; // "index" / "delete"
private $type; // DTO FQN
private $id; // Database ID
public function __construct(string $type, string $id, string $operation = IndexationRequestHandler::OP_INDEX)
{
$this->type = $type;
$this->id = $id;
$this->operation = $operation;
}
...
class IndexationRequestHandler implements MessageHandlerInterface
{
public function __invoke(IndexationRequest $message)
{
$model = // todo fetch the model
$doc = new Document($id, $model, '_doc')
$indexer->scheduleIndex($indexName, $doc);
$indexer->flush();
}
}
Send update request:
$this->bus->dispatch(
new IndexationRequest(User::class, 123, 'index')
);
Read the Messages:
$ bin/console messenger:consume-messages
We can switch Elastica HTTP Client!
Elastica\Transport\AbstractTransport
Just one class to implement π
Available in Elastically
Put a Node directly on your PHP servers,
get the power of localhost:9200
!
# .symfony/services.yaml
mysearch:
type: "elasticsearch:7.2"
disk: 1024
configuration:
plugins:
- analysis-icu
# .symfony.cloud.yaml
relationships:
elasticsearch: "mysearch:elasticsearch"
Open local Kibana to production Elasticsearch
$ symfony tunnel:open
$ docker run -it --rm --network host \
-e ELASTICSEARCH_URL=http://127.0.0.1:30002 \
docker.elastic.co/kibana/kibana:7.2.1
πΏ => πΏ, pop-corn
π => π, cheese, pizza
https://github.com/jolicode/elastically
@damienalexandre
coucou@jolicode.com
Priscilla Du Preez
Aaron Burden
Suzanne D. Williams
Anthony Martino
Alexandre Godreau
David Clode
Vincent Botta
Brooke Lark
Winner ShutterStock
JoliCode, Perrine, Nicolas Ruflin.