Symfony2でElasticSearchを使用して製品プロパティで最も簡単なフィルターを作成する

インターネット上に既製のステップバイステップガイド「ElasticSearchで製品フィルターを実装する方法」がないため、この記事を書くことに触発されました。これを行うタスクは明確で揺るぎないものでした。 断片的な参照情報を見つけることはできましたが、最も些細な問題を解決するための料理本は見つかりませんでした。



FOSElasticaBundleを使用するため、symfony2に焦点を当てます。これにより、便利なyaml構成でelasticsearchインデックスのマッピングを記述し、Doctrine ORMエンティティまたはDoctrine ODMドキュメントをバインドできます。 工業化されたインデックスは、単一のコンソールコマンドで関連するドクトリンエンティティから生成されます。 さらに、検索の構築とクエリのフィルタリングのためのベンダーライブラリが含まれています。 検索結果は、エンティティオブジェクトの配列または検索インデックスに添付されたDoctrine ORM / ODMドキュメントとして返されます。 FOSElasticaBundleの詳細については、伝統的にgithubにあります: github.com/FriendsOfSymfony/FOSElasticaBundle



バンドルを使用すると、純粋なJSONでの操作を完全に無視し、json_encodeおよびjson_decode関数で何かをエンコードおよびデコードし、curlの助けを借りてどこかに登ることができます。 これが唯一のOOPアプローチです!



SQLのデータスキーマについて少し


製品はリレーショナルDBMSに保存されているため、プロパティと値のEAVモデルを実装する必要がありました(詳細については、 en.wikipedia.org / wiki / Entity%E2%80%93attribute%E2%80%93value_model



その結果、次のデータスキームが得られました。

画像





基本ダンプ: drive.google.com/file/d/0B30Ybwiexqx6S1hCanpISHVvcjQ/edit?usp=sharing

これを使用して、Doctrineエンティティを作成し、ElasticSearchにマッピングします。



ElasticSearchのMapim EAVモデル


最初にFOSElasticaBundleをインストールします。 composer.jsonでは、次を指定する必要があります。



"friendsofsymfony/elastica-bundle": "dev-master"
      
      







依存関係を更新し、確立されたバンドルをAppKernel.phpに登録します。



 new FOS\ElasticaBundle\FOSElasticaBundle()
      
      







ここで、config.ymlで次の設定を規定します。



 fos_elastica: clients: default: { host: localhost, port: 9200 } indexes: test: types: product: mappings: name: ~ price: ~ category: ~ productsOptionValues: type: "object" properties: productOption: index: not_analyzed value: type: string index: not_analyzed persistence: driver: orm model: Vendor\TestBundle\Entity\Product provider: ~ listener: immediate: ~ finder: ~
      
      







上記で作成したインデックスにデータを入力するには、コンソールコマンドphp app / console fos:elastica:populateを実行します。 その結果、FOSElasticaBundleはデータベースのデータをインデックスに追加します。



注:ネストされたオブジェクトの形式で製品内に、特性とその値を囲みます。 すべてが正常に機能するためには、productsOptionValuesの特性のコレクションにtype: "nested"ではなく、正確にtype: "object"を指定する必要があります。 それ以外の場合、特性は次のように配列に格納されます: www.elasticsearch.org/guide/en/elasticsearch/guide/current/complex-core-fields.html#_arrays_of_inner_objectsおよびフィルターは正しく機能しません。 また、文字列インデックスnot_analyzedが原因である、フィルタリングされたフィールドは分析されるべきではないことに注意する必要があります。 そうしないと、スペースを含む文字列をフィルタリングするときに問題が発生します。



localhost:9200 / test / product / _search?Prettyに埋め込まれた特性を持つ製品のリストを見ることができますか?私の場合、サーバーの応答は次のようになります:

gist.github.com/ArFeRR/3976778079d64d5a72cd



フィルタリングフォームをレンダリングします。




フォーム自体は次のとおりです。





コントローラーでは、すべてのプロパティと商品を取得する要求を満たし、空のフィルター配列を宣言し、これをすべてTWIGテンプレートに転送します。



 $options = $entityManager->getRepository("ParfumsTestBundle:ProductOption")->findAll(); $products = $entityManager->getRepository("ParfumsTestBundle:Product")->findAll(); $request = $this->get('request'); $filter = $request->query->get('filter'); return $this->render('ParfumsTestBundle:Default:filter.html.twig', array('options'=>$options, 'products' => $products, 'filter' => $filter));
      
      







ここでは、フォームでプロパティ名が重複しないようにプロパティ名でグループ化する必要がありますが、スペースを節約するため、プロパティ名ではグループ化しません。 エンティティ/ドキュメントリポジトリに自分でDQLクエリを記述します。 フィルタで何も選択されていない場合、製品のリスト全体を表示するには、FindAll製品リクエストが必要です。



そして、ここに小枝自体があります:

 {% extends "TwigBundle::layout.html.twig" %} {% block body %} <h1></h1> <form> <ul> {% for option in options %} <li> {{ option.name }} <ul> {% for value in option.productsOptionValues %} <li> <input type="checkbox" value="{{ value.value }}" name="filter[{{ option.name }}][{{ value.id }}]" {% if filter[option.name][value.id] is defined %} checked="checked" {% endif %} /> {{ value.value }} </li> {% endfor %} </ul> </li> {% endfor %} </ul> <input type="submit" /> </form> <h1></h1> <table> {% for product in products %} <tr> <td>{{ product.name }}</td> <td>{{ product.price }}</td> <td> {% for option_value in product.productsOptionValues %} {{ option_value.productOption }} : {{ option_value.value }} <br /> {% endfor %} </td> </tr> {% endfor %} </table> {% endblock %}
      
      







ろ過フォームを処理します


楽しい部分に行きましょう。

次に、処理のためにElasticSearch'yに渡される検索クエリ(より正確にはJSONフィルター)を作成する必要があります。 これは、FOSElasticaBundleに組み込まれているElastica.ioライブラリを使用して行われます(詳細: elastica.io

そのため、コントローラーのアクションで、フォームから受け取ったフィルター配列を処理します。



 if(!empty($filter)) { $finder = $this->container->get('fos_elastica.finder.parfums.product'); $andOuter = new \Elastica\Filter\Bool(); foreach($filter as $optionKey=>$arrValues) { $orOuter = new \Elastica\Filter\Bool(); foreach($arrValues as $value) { $andInner = new \Elastica\Filter\Bool(); $optionKeyTerm = new \Elastica\Filter\Term(); $optionKeyTerm->setTerm('productOptionValues.productOption', $optionKey); $valueTerm = new \Elastica\Filter\Term(); $valueTerm->setTerm('productOptionValues.value', $value); $andInner->addMust($optionKeyTerm); $andInner->addMust($valueTerm); $orOuter->addShould($andInner); } $andOuter->addMust($orOuter); } $filtered = new \Elastica\Query\Filtered(); $filtered->setFilter($andOuter); $products = $finder->find($filtered); }
      
      







ここでは、配列をアドレスバーに渡し、ユーザーが選択したフィルター値を反復処理して、ElasticaライブラリがElasticSearchがデータセットをフィルター処理するJSON行を生成するクラスオブジェクトのツリーのような構造を作成します。

gist.github.com/ArFeRR/97671e54515dfd7be012



このJSONは、リレーショナルデータベースの次の条件にほぼ一致します。

WHERE((オプション=解像度AND値= 1980x1020)OR(オプション=解像度AND値= 1600x900))AND(オプション=重量AND値= 2.7 kg)



その結果、結果として、ユーザーが選択した2つの重量のうち、同じ重量と少なくとも1つの画面解像度を持つ商品を取得する必要があります。 私のデータセットでは-これは1つの製品のみです。







すべてが正しく機能しているようです。



上記のフィルタリングの例をさらに発展させることができます。 次のステップは、関連性、結果のページネーション、および集計の調整(ESでのファセットのプライベート実装)によって結果をソートする実装です。 Habrコミュニティーにとって興味深いものになる場合は、このことについて後で説明します。



upd0:
読者の要求に応じて、フィルターフォームハンドラーは安全なオブジェクトSymfony \ Component \ HttpFoundation \ Requestを使用して書き換えられました。 アクションに埋め込む(パラメータとして渡す)か、アクションで$ request = $ this-> get( 'request')を介してサービスから受信する必要があります。



All Articles