On Github jolicode / elastica-conf
…Poney, Guinness et gif animé.
"(($wpdb->posts.post_title LIKE '{$n}{$term}{$n}') OR ($wpdb->posts.post_content LIKE '{$n}{$term}{$n}'))";WordPress has killer search baked in. Every word you write is fully searchable […]
{ "content": "Yolo", "created_by": { "name": "Damin0u" } }La dénormalisation va impacter vos capacités à effectuer des recherches, on veut pas exemple le USER dans le FICHIER.
Nous stockons les documents dans des types, eux-mêmes inclus dans un index.
http://localhost:9200/yolo/test/1Et cela ce reflette dans l'url, on est en REST
curl -XPOST "http://localhost:9200/yolo/test/1"
{ "post_date": "2013/10/30 13:37:00", "content": "Elasticsearch is swag.", "author": { "name": "Damien", } }La plus simple reste les espaces, chaque "mot" devient un token.
curl -XPOST "http://localhost:9200/yolo/test/_search"
{ "query": { "match_all" : {} } }Toujours écrire ces requêtes en JSON avant de jouer en PHP.
Chaque prestation d'un artiste est un document :
{ name: "Black Sabbath", listeners: 2128492, description: "Black Sabbath is an English heavy...", festival: "Hellfest 2014", year: 2014, date: "2014, Sunday 22 June", stage: "Main Stage 01", tags: [ "heavy metal", "hard rock", "classic rock", "metal" ] }
composer require ruflin/Elastica dev-master
./composer.json has been updated Loading composer repositories with package information Updating dependencies (including require-dev) - Installing ruflin/elastica (dev-master 62f63df) Cloning 62f63df4d093f325b6848fa0bedae23da6ece3d7
<?php include("vendor/autoload.php"); // default to localhost:9200, no config! $elasticaClient = new \Elastica\Client(); /** @var $festivalIndex \Elastica\Index */ $festivalIndex = $elasticaClient->getIndex('festival'); $festivalIndex->exists(); // => false
<?php $festivalIndex->create( array( 'number_of_shards' => 1, 'number_of_replicas' => 0, // Analysis here too ), true // delete index if it already exists );
/** @var $hellfestType \Elastica\Type */ $hellfestType = $festivalIndex->getType('hellfest');Ça ne crée pas vraiment le type... hihi Mapping ici.
$gigs = json_decode(file_get_contents("data-merged.json"), true); // Slow foreach ($gigs as $gig) { $hellfestType->addDocument( new \Elastica\Document('', $gig) ); }
Une requête HTTP par Document !
Dénormalisation déjà faite. Perf horrible.$docs = array(); foreach ($gigs as $gig) { $docs[] = new \Elastica\Document('', $gig); } // Only one request! $hellfestType->addDocuments($docs);
Créez des bulks de taille raisonnable et abusez-en.
Comme en SQL, on peux faire des INSERT multiplemarvel.agent.enabled: false
GET festival/hellfest/_search
{ "query": { "match": { "name": "slayer" } } }
GET INDEX/TYPE/_search { "query": { "TYPE DE RECHERCHE": { "PARAMÉTRES" } } }
$elasticaClient = new \Elastica\Client(); // localhost:9200 /** @var $festivalIndex \Elastica\Index */ $festivalIndex = $elasticaClient->getIndex('festival'); /** @var $hellfestType \Elastica\Type */ $hellfestType = $festivalIndex->getType('hellfest');
À rendre disponible sous forme de service dans votre application.
$matchQuery = new \Elastica\Query\Match(); $matchQuery->setField('name', 'Slayer'); // Not mandatory $searchQuery = new \Elastica\Query(); $searchQuery->setQuery($matchQuery); $resultSet = $hellfestType->search($matchQuery);
Traduire de gauche à droite !
GET festival/hellfest/_search // getType()->search() { "query": { // new \Elastica\Query() "match": { // new \Elastica\Query\Match() "name": "slayer" // setField() } } }
Les namespaces respectent la hiérarchie.
{ "took": 1, "timed_out": false, "_shards": { ... }, "hits": { "total": 4, "max_score": 6.2214365, "hits": [ { "_index": "festival", "_type": "hellfest", "_id": "NPs8-jFuQUShDVSvQazOzQ", "_score": 6.2214365, "_source": { "tags": [ "thrash metal", ... ], "listeners": 1226045, "description": "Slayer is a thrash metal band...", "name": "Slayer" ... } }, ... ] } }
class Elastica\ResultSet { protected $_results => array(4) { [0] => class Elastica\Result { protected $_hit => array(5) { '_index' => "festival" '_type' => "hellfest" '_id' => "CwBTEcsnSgeVsvP52Wjurw" '_score' => double(6.2214365) '_source' => array(8) { ... } } }, ... } protected $_response => class Elastica\Response protected $_query => class Elastica\Query // gift! protected $_took => int(3) protected $_timedOut => bool(false) protected $_totalHits => int(4) protected $_maxScore => double(6.2214365) }
echo sprintf("Slayer a fait %d Hellfest :\n", $resultSet->getTotalHits()); foreach ($resultSet->getResults() as $result) { /** @var $result \Elastica\Result */ $data = $result->getData(); echo $data['festival']."\n"; }
Slayer a fait 4 Hellfest : Hellfest 2008 Open-Air Edition Hellfest 2007 Hellfest 2010 Hellfest 2014
Et Elastica c'est facile...
$matchQuery->setField('name', 'Motorhead'); $resultSet = $hellfestType->search($matchQuery); echo sprintf("Motorhead a fait %d Hellfest\n", $resultSet->getTotalHits());
$matchQuery->setField('name', 'Motörhead'); $resultSet = $hellfestType->search($matchQuery); echo sprintf("Motörhead a fait %d Hellfest\n", $resultSet->getTotalHits());
Rien à voir avec Tolkien.
Séparer les contenus en morceaux facile à trouver.
$festivalIndex->create(array( 'number_of_shards' => 1, 'number_of_replicas' => 0, 'analysis' => array( 'analyzer' => array( 'my_awesome_analyzer' => array( 'type' => 'custom', 'tokenizer' => 'standard', 'filter' => array('lowercase', 'asciifolding') ), ) ) ), true );Ça se fait au niveau de l'index. Replica = meilleures perf et redondance.
$mapping = new \Elastica\Type\Mapping(); $mapping->setType($hellfestType); $mapping->setProperties(array( 'name' => array( 'type' => 'string', 'analyzer' => 'my_awesome_analyzer', 'boost' => 3 ), 'description' => array('type' => 'string', 'analyzer' => 'my_awesome_analyzer'), 'tags' => array('type' => 'string', 'boost' => 2, 'index' => 'not_analyzed'), )); $mapping->send();
$matchQuery->setField('name', 'Motorhead'); $resultSet = $hellfestType->search($matchQuery); echo sprintf("Motorhead a fait %d Hellfest\n", $resultSet->getTotalHits());
J'aime le progressive metal mais je déteste le grindcore, quels groupes puis-je aller voir en 2014 ?
'tags' => array( 'type' => 'string', 'boost' => 2, 'index' => 'not_analyzed' ),
not_analyzed est parfait pour un tag, le token === le mot.
{ "term": { "tags": { "value": "progressive metal" } } }
{ "term": { "year": { "value": 2014 } } }
{ "bool": { "must": [ // Queries to match ], "must_not": [ // Queries to not match ] } }
$progressiv = new \Elastica\Query\Term(array( 'tags' => array('value' => 'progressive metal') )); $year = new \Elastica\Query\Term(array( 'year' => array('value' => 2014) )); $grindcore = new \Elastica\Query\Term(array( 'tags' => array('value' => 'grindcore') ));
$boolQuery = new \Elastica\Query\Bool(); $boolQuery->addMust($progressiv); $boolQuery->addMust($year); $boolQuery->addMustNot($grindcore); $resultSet = $hellfestMappingType->search($boolQuery); echo sprintf("Il y a %d groupes que tu aime au Hellfest %d\n", $resultSet->getTotalHits(), 2014);
Elasticsearch est aussi un super moteur de statistiques !
Les facettes ne sont pas officiellement dépréciées, mais utilisez les agrégations !
GET festival/hellfest/_search
{ "size": 0, "aggs": { "by_year": { "terms": { "field": "year", "size": 50 } } } }
SELECT COUNT(*) FROM gigs GROUP BY year
$yearsAgg = new \Elastica\Aggregation\Terms("by_year"); $yearsAgg->setSize(50); $yearsAgg->setField('year'); $search = new \Elastica\Query(); $search->setSize(0); $search->addAggregation($yearsAgg); $results = $type->search($search); $years = $results->getAggregation('by_year');
Sky is the limit.
Pour les gros bourrins du bulk :
$index->setSettings(array( "refresh_interval" => "-1" ));
$index->setSettings(array( "refresh_interval" => "1s" )); $index->optimize();Surtout utile si vous attaquez l'index avec plein de workers
Faites toujours pointez vos recherches sur un alias !
Vous pouvez demander uniquement quelques champs de votre source. Voir aucun.
$query = Query::create($myQuery); $query->setFields(['_id']);
L'admin réseau vous dira merci.
<?php use Elastica\Util; echo Util::convertRequestToCurlCommand( $type->getIndex()->getClient()->getLastRequest() );curl -XGET 'http://host:9200/index/type/_search' -d '{"query":{"query_string":{"query":"coucou"}}}'
This is madness!
new \Elastica\Query\Term(); new \Elastica\Filter\Term();
Bonne utilisation des use.
use Elastica\Query as Query; use Elastica\Filter as Filter; // after new Query\Term(); new Filter\Term();
$matching = new \Elastica\Query\Bool(); $matchQuery = new \Elastica\Query\Match(); $matchQuery->setField('name', $terms); $mathing->addShould($matchQuery); $matchQuery = new \Elastica\Query\Match(); $matchQuery->setField('pays', $terms); $mathing->addShould($matchQuery); $matchQuery = new \Elastica\Query\Match(); $matchQuery->setField('bio', $terms); $mathing->addShould($matchQuery); $matchQuery = new \Elastica\Query\Match(); $matchQuery->setField('job', $terms); $mathing->addShould($matchQuery);
$matching = new Query\MultiMatch(); $matching->setQuery($terms); $matching->setParam('use_dis_max', false); $matching->setFields(array( 'name', 'bio', 'pays', 'job', ));
$matching = new Query\MultiMatch(); $matching->setQuery($terms); $matching->setParam('use_dis_max', false); $matching->setFields(array( 'name^10', 'bio^4', 'pays', 'job', ));
new Query\Bool();
Toujours utile pour ajouter des must ou must_not à la volée !
Pour copier/coller la doc !
$query = '{"query": {"match_all": {}}}'; $path = $index->getName() . '/' . $type->getName() . '/_search'; $response = $client->request($path, Request::GET, $query); $responseArray = $response->getData();
Pas de ResultSet en retour ☹
Utilisez FOSElasticaBundle !
Elastica.io ne suffit pas.
https://github.com/ruflin/Elastica/tree/master/test/lib/Elastica/Test
Souvent quelques version de retard sur Elasticsearch : default[:elasticsearch][:version] = "1.1.1"
env: global: - ES_WAIT_ON_MAPPING_CHANGE=true
Non documenté, mais permet de ne lancer les tests que sur un node prêt !
Elasticsearch, ce n'est pas que de la recherche.
Intro / PHP Weapon / Internet unicorn / Slayer / Animated gifs / Animated gifs / Veste a patch / Favicon
Elasticsearch, the definitive guide / Compilation de ressources pour débuter