EC-CUBEの検索に形態素解析エンジン（MeCab）を実装するサンプル

何だかスゴそうですがスゴいのはMeCabを開発した人でこの記事自体は大したこと書いてません。形態素解析やMeCabについては以下のwikiを読んでもらうとなんとなくイメージがつくと思います。
[形態素解析](http://ja.wikipedia.org/wiki/%E5%BD%A2%E6%85%8B%E7%B4%A0%E8%A7%A3%E6%9E%90)
[MeCab](http://ja.wikipedia.org/wiki/MeCab)

#つまり何？
大まかにいうと「文章を主語・述語・形容詞・動詞・助動詞などに分割しよう」というのが形態素解析だとご理解ください。私自身それ以上ツッコんで理解してません。

#で？
例えば「50番のなべ」を検索する場合、実際には「『50』か『なべ』に該当すればいい」と考える場合は多いはずです。それをいちいちスペースで「50」と「なべ」と区切ることなく検索できたら便利かも知れません。のでそれをEC-CUBEに実装してみましょう。というお話です。

#実装に必要なもの
・MeCab
・EC-CUBE（2.13.3ベースで説明してます）

#MeCabインストール
[こちら](http://qiita.com/Keech/items/3b51a60c89b9e803b256)の通りです。

#EC-CUBEの改造
・*/data/class_extends/page_extends/products/LC_Page_Products_List_Ex.php*
以下のファンクションを適当な位置にコピペ<pre>
/**
 * 検索条件のwhere文とかを取得
 *
 * @return array
 */
public function lfGetSearchCondition($arrSearchData)
{
    $searchCondition = array(
        'where'             => '',
        'arrval'            => array(),
        'where_category'    => '',
        'arrvalCategory'    => array()
    );

    // カテゴリからのWHERE文字列取得
    if ($arrSearchData['category_id'] != 0) {
        list($searchCondition['where_category'], $searchCondition['arrvalCategory']) = SC_Helper_DB_Ex::sfGetCatWhere($arrSearchData['category_id']);
    }
    // ▼対象商品IDの抽出
    // 商品検索条件の作成（未削除、表示）
    $searchCondition['where'] = SC_Product_Ex::getProductDispConditions('alldtl');

    if (strlen($searchCondition['where_category']) >= 1) {
        $searchCondition['where'] .= ' AND EXISTS (SELECT * FROM dtb_product_categories WHERE ' . $searchCondition['where_category'] . ' AND product_id = alldtl.product_id)';
        $searchCondition['arrval'] = array_merge($searchCondition['arrval'], $searchCondition['arrvalCategory']);
    }

    // 商品名をwhere文に
    $name = $arrSearchData['name'];
    $name = $this->MorphologicalAnalysis($name);	// 追加箇所
    $name = str_replace(',', '', $name);
    // 全角スペースを半角スペースに変換
    $name = str_replace('　', ' ', $name);
    // スペースでキーワードを分割
    $names = preg_split('/ +/', $name);

    // 分割したキーワードを一つずつwhere文に追加
    foreach ($names as $val) {
        if (strlen($val) > 0) {
            $searchCondition['where']    .= ' AND ( alldtl.name ILIKE ? OR alldtl.comment3 ILIKE ?) ';
            $searchCondition['arrval'][]  = "%$val%";
            $searchCondition['arrval'][]  = "%$val%";
        }
    }

    // メーカーらのWHERE文字列取得
    if ($arrSearchData['maker_id']) {
        $searchCondition['where']   .= ' AND alldtl.maker_id = ? ';
        $searchCondition['arrval'][] = $arrSearchData['maker_id'];
    }

    // 在庫無し商品の非表示
    if (NOSTOCK_HIDDEN) {
        $searchCondition['where'] .= ' AND EXISTS(SELECT * FROM dtb_products_class WHERE product_id = alldtl.product_id AND del_flg = 0 AND (stock >= 1 OR stock_unlimited = 1))';
    }

    // XXX 一時期内容が異なっていたことがあるので別要素にも格納している。
    $searchCondition['where_for_count'] = $searchCondition['where'];

    return $searchCondition;
}

/**
 * 形態素解析による抜き出し
 *
 */
function MorphologicalAnalysis($txt){
    // 半角英数字のみの場合はそのまま
    if(preg_match("/^[!-~]+$/", $txt)){
        return $txt;
    }
    $mecab = new MeCab_Tagger();
    $meisi = array();
    $word = '';
    for($node=$mecab->parseToNode($txt); $node; $node=$node->getNext())
    {
        // 取捨選択が難しいので名詞のみの取得はやめ
        if( $node->getStat() != 2 && $node->getStat() != 3 && mb_strpos($node->getFeature(), '名詞', NULL, 'utf-8')===0)
        {
            $word = $node->getSurface();
            if (mb_strlen($word)>1)
            array_push($meisi, $word);  // 分解したとき１文字になるものは検索から除外
        }
    }
    return implode(" ", $meisi);
}
</pre>

#テスト
「50番のなべ」で検索すると…
![dc14d08e7895c754ce80aa9d37d88078.png](https://qiita-image-store.s3.amazonaws.com/0/75602/6af91abd-b976-f01c-c6a6-107186b71f27.png)

「おなべ50」と「おなべレシピ50」が検索される
![2ff34c0beacfecd8b7be848db3abd842.png](https://qiita-image-store.s3.amazonaws.com/0/75602/8c514ea6-b3e2-ebc8-14df-a2d9226e7855.png)

#実際に実装してみて
上記のMorphologicalAnalysisは
・文章中の名詞のみを抜き出す
・分割して一文字になるものは除外する
となっているため「商品名が名詞一文字を連結した造語」である場合、検索に引っかからなくなる不具合が発生しました。つまり弊社で提供している「腰楽」という商品が引っかからなくなったため以下のように修正して運用しています。

<pre>
/**
 * 形態素解析による抜き出し
 *
 */
function MorphologicalAnalysis($txt){
    // 半角英数字のみの場合はそのまま
    if(preg_match("/^[!-~]+$/", $txt)){
        return $txt;
    }
    $mecab = new MeCab_Tagger();
    $meisi = array();
    $word = '';
    for($node=$mecab->parseToNode($txt); $node; $node=$node->getNext())
    {
        //if( $node->getStat() != 2 && $node->getStat() != 3 && mb_strpos($node->getFeature(), '名詞', NULL, 'utf-8')===0)
        {
            $word = $node->getSurface();
            //if (mb_strlen($word)>1)
            array_push($meisi, $word);          }
　　　　 }
    }
    return implode(" ", $meisi);
}
</pre>