{
    "componentChunkName": "component---src-templates-blog-post-jsx",
    "path": "/post/how-to-implement-fulltext-search-with-fuelphp/",
    "result": {"data":{"site":{"siteMetadata":{"title":"WEB EGG","author":"Leko - CTO at Yuimedi"}},"markdownRemark":{"id":"1d51f844-2116-55b2-90e3-52232cd83cee","excerpt":"こんにちは。お久しぶりの更新です。 「入力内容に対する類似テキストの検索」を実装する機会があったので、FuelPHPでInnoDBの全文検索を利用してみました。 とはいえFuel…","html":"<p>こんにちは。お久しぶりの更新です。</p>\n<p>「入力内容に対する類似テキストの検索」を実装する機会があったので、<a href=\"http://fuelphp.jp/docs/1.7/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">FuelPHP</a>でInnoDBの全文検索を利用してみました。<br>\nとはいえFuel自体には全文検索をサポートする操作は特に無いので、だいたい自前で書きました。</p>\n<p>手間無くそこそこの精度が出せたので、導入から使用例までメモとして残します。</p>\n<!--more-->\n<h2 id=\"前提\" style=\"position:relative;\"><a href=\"#%E5%89%8D%E6%8F%90\" aria-label=\"前提 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>前提</h2>\n<p>このような環境での動作確認しています。</p>\n<table>\n<thead>\n<tr>\n<th>名前</th>\n<th>バージョン</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>OS</td>\n<td>CentOS 6.5(64bit)</td>\n</tr>\n<tr>\n<td>MySQL</td>\n<td>5.6.20</td>\n</tr>\n<tr>\n<td>PHP</td>\n<td>5.4.37</td>\n</tr>\n<tr>\n<td>MeCab</td>\n<td>0.996</td>\n</tr>\n</tbody>\n</table>\n<p>最低限必要なのはMySQLのバージョンです。<br>\n<strong>InnoDBのFULLTEXTインデックスはMySQL 5.6.4からのみ利用できます。</strong><sup id=\"fnref-754:1\"><a href=\"#fn-754:1\" rel=\"footnote\">1</a></sup></p>\n<p>また、InnoDBのFULLTEXTは空白区切りの単語検索しか対応していない<sup id=\"fnref-754:2\"><a href=\"#fn-754:2\" rel=\"footnote\">2</a></sup>ため、<br>\n<a href=\"http://taku910.github.io/mecab/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Mecab</a>を使用して分かち書きして保存・検索します。</p>\n<p>MySQL, PHPはインストール済みの前提で話を進めます。<br>\nまた、Fuelの基本的な知識があり、セットアップは済んでいるものとします。<br>\nMeCabは次の環境構築にてインストールしていきます。</p>\n<h2 id=\"環境構築\" style=\"position:relative;\"><a href=\"#%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89\" aria-label=\"環境構築 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>環境構築</h2>\n<blockquote>\n<p>phpでmecabを使う手順<br>\n<a href=\"http://qiita.com/Keech/items/3b51a60c89b9e803b256\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">http://qiita.com/Keech/items/3b51a60c89b9e803b256</a></p>\n</blockquote>\n<p>こちらの記事を参考に環境構築をしたのですが、自分の環境ではコピー&#x26;ペーストでは動かない箇所があったのでそれを込みでインストールコマンド全てを貼り付けます。</p>\n<p><code>sudo</code>は省略しているため、コマンドが動かない場合はルートになるか適宜sudoの追加をして下さい。</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\"><span class=\"token comment\"># my.cnfにInnoDBの全文検索用の設定を追加。既にして有れば不要です</span>\n<span class=\"token function\">sed</span> -i -e <span class=\"token string\">'s|\\[mysqld\\]|[mysqld]\\n#fulltext index\\ninnodb_ft_min_token_size=2\\n|'</span> /etc/my.cnf\n\n<span class=\"token comment\"># --- Mecab</span>\nyum <span class=\"token function\">install</span> -y gcc-c++\n<span class=\"token builtin class-name\">cd</span> /tmp\n<span class=\"token function\">wget</span> https://mecab.googlecode.com/files/mecab-0.996.tar.gz\n<span class=\"token function\">tar</span> zxfv mecab-0.996.tar.gz\n<span class=\"token builtin class-name\">cd</span> mecab-0.996\n./configure --enable-utf8-only\n<span class=\"token function\">make</span>\n<span class=\"token function\">make</span> <span class=\"token function\">install</span>\n\n<span class=\"token comment\"># --- Mecab IPAdic</span>\n<span class=\"token function\">wget</span> https://mecab.googlecode.com/files/mecab-ipadic-2.7.0-20070801.tar.gz\n<span class=\"token function\">tar</span> zxfv mecab-ipadic-2.7.0-20070801.tar.gz\n<span class=\"token builtin class-name\">cd</span> mecab-ipadic-2.7.0-20070801\n./configure --with-mecab-config<span class=\"token operator\">=</span><span class=\"token punctuation\">..</span>/mecab-config --with-charset<span class=\"token operator\">=</span>utf8\n<span class=\"token function\">make</span>\n<span class=\"token function\">make</span> <span class=\"token function\">install</span>\n\n<span class=\"token comment\"># --- php-mecab</span>\nyum <span class=\"token function\">install</span> -y php-devel --enablerepo<span class=\"token operator\">=</span>remi\n<span class=\"token builtin class-name\">cd</span> /tmp\n<span class=\"token function\">wget</span> https://github.com/rsky/php-mecab/archive/master.zip\n<span class=\"token function\">unzip</span> master.zip\n<span class=\"token builtin class-name\">cd</span> php-mecab-master/mecab\nphpize\n./configure --with-php-config<span class=\"token operator\">=</span>/usr/bin/php-config --with-mecab<span class=\"token operator\">=</span>/usr/local/bin/mecab-config\n<span class=\"token function\">make</span>\n<span class=\"token function\">make</span> <span class=\"token function\">install</span>\n<span class=\"token function\">ln</span> -s /usr/lib64/php/modules/mecab.so /etc/php.d\n<span class=\"token function\">sed</span> -i -e <span class=\"token string\">'s|; default extension directory\\.|; default extension directory.\\nextension=mecab.so|'</span> /etc/php.ini</code></pre></div>\n<p>InnoDBの全文検索用の設定はこちらの記事が参考になりました。</p>\n<blockquote>\n<p>MySQL5.6でInnoDBのFULLTEXT INDEXで全文検索する <a href=\"http://www.petitec.com/2013/04/mysql5-6-fulltext-index/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">http://www.petitec.com/2013/04/mysql5-6-fulltext-index/</a></p>\n</blockquote>\n<p>インストールできたか確認しておきます。</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">$ <span class=\"token builtin class-name\">echo</span> <span class=\"token string\">'東京特許許可局局長'</span> <span class=\"token operator\">|</span> mecab\n東京  名詞,固有名詞,地域,一般,*,*,東京,トウキョウ,トーキョー\n特許  名詞,サ変接続,*,*,*,*,特許,トッキョ,トッキョ\n許可  名詞,サ変接続,*,*,*,*,許可,キョカ,キョカ\n局   名詞,接尾,一般,*,*,*,局,キョク,キョク\n局長  名詞,一般,*,*,*,*,局長,キョクチョウ,キョクチョー\nEOS\n\n$ php -i <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> -i mecab\nmecab\nMeCab Support <span class=\"token operator\">=</span><span class=\"token operator\">></span> enabled\nMeCab Library <span class=\"token operator\">=</span><span class=\"token operator\">></span> <span class=\"token number\">0.996</span> <span class=\"token operator\">=</span><span class=\"token operator\">></span> <span class=\"token number\">0.996</span>\nmecab.default_dicdir <span class=\"token operator\">=</span><span class=\"token operator\">></span> no value <span class=\"token operator\">=</span><span class=\"token operator\">></span> no value\nmecab.default_rcfile <span class=\"token operator\">=</span><span class=\"token operator\">></span> no value <span class=\"token operator\">=</span><span class=\"token operator\">></span> no value\nmecab.default_userdic <span class=\"token operator\">=</span><span class=\"token operator\">></span> no value <span class=\"token operator\">=</span><span class=\"token operator\">></span> no value\n<span class=\"token environment constant\">OLDPWD</span> <span class=\"token operator\">=</span><span class=\"token operator\">></span> /tmp/php-mecab-master/mecab\n_SERVER<span class=\"token punctuation\">[</span><span class=\"token string\">\"OLDPWD\"</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span><span class=\"token operator\">></span> /tmp/php-mecab-master/mecab</code></pre></div>\n<p>こんな感じの出力になっていればOKです。</p>\n<h2 id=\"全文検索用のカラムを追加する\" style=\"position:relative;\"><a href=\"#%E5%85%A8%E6%96%87%E6%A4%9C%E7%B4%A2%E7%94%A8%E3%81%AE%E3%82%AB%E3%83%A9%E3%83%A0%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B\" aria-label=\"全文検索用のカラムを追加する permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>全文検索用のカラムを追加する</h2>\n<p>今回は、「すでに作成済みのテーブルに全文検索の仕組みを入れる」というシチュエーションで行きます。</p>\n<p>記事用のサンプルとして、こんなテーブルが存在するとします。<br>\n名前は適当に<code>books</code>とでもしておきます。</p>\n<table>\n<thead>\n<tr>\n<th>カラム名</th>\n<th>型</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>title</td>\n<td>VARCHAR(50)</td>\n</tr>\n<tr>\n<td>content</td>\n<td>TEXT</td>\n</tr>\n</tbody>\n</table>\n<p>このテーブルに全文検索用のカラム<code>content_splited</code>を追加します。</p>\n<table>\n<thead>\n<tr>\n<th>カラム名</th>\n<th>型</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>content_splited</td>\n<td>TEXT</td>\n</tr>\n</tbody>\n</table>\n<p>Fuelのマイグレーションファイルに直すとこんな感じです。</p>\n<div class=\"gatsby-highlight\" data-language=\"php\"><pre class=\"language-php\"><code class=\"language-php\"><span class=\"token php language-php\"><span class=\"token delimiter important\">&lt;?php</span>\n\n<span class=\"token keyword\">namespace</span> <span class=\"token package\">Fuel<span class=\"token punctuation\">\\</span>Migrations</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">class</span> <span class=\"token class-name-definition class-name\">Add_content_splited_to_demands</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">function</span> <span class=\"token function-definition function\">up</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token class-name class-name-fully-qualified static-context\"><span class=\"token punctuation\">\\</span>DBUtil</span><span class=\"token operator\">::</span><span class=\"token function\">add_fields</span><span class=\"token punctuation\">(</span><span class=\"token string single-quoted-string\">'books'</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">array</span><span class=\"token punctuation\">(</span>\n            <span class=\"token string single-quoted-string\">'content_splited'</span> <span class=\"token operator\">=></span> <span class=\"token keyword\">array</span><span class=\"token punctuation\">(</span>\n                <span class=\"token string single-quoted-string\">'type'</span>  <span class=\"token operator\">=></span> <span class=\"token string single-quoted-string\">'text'</span><span class=\"token punctuation\">,</span>\n                <span class=\"token string single-quoted-string\">'after'</span> <span class=\"token operator\">=></span> <span class=\"token string single-quoted-string\">'content'</span><span class=\"token punctuation\">,</span>\n            <span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n        <span class=\"token class-name class-name-fully-qualified static-context\"><span class=\"token punctuation\">\\</span>DBUtil</span><span class=\"token operator\">::</span><span class=\"token function\">create_index</span><span class=\"token punctuation\">(</span><span class=\"token string single-quoted-string\">'books'</span><span class=\"token punctuation\">,</span> <span class=\"token string single-quoted-string\">'content_splited'</span><span class=\"token punctuation\">,</span> <span class=\"token string single-quoted-string\">'content_splited'</span><span class=\"token punctuation\">,</span> <span class=\"token string single-quoted-string\">'fulltext'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">public</span> <span class=\"token keyword\">function</span> <span class=\"token function-definition function\">down</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">{</span>\n        <span class=\"token class-name class-name-fully-qualified static-context\"><span class=\"token punctuation\">\\</span>DBUtil</span><span class=\"token operator\">::</span><span class=\"token function\">drop_fields</span><span class=\"token punctuation\">(</span><span class=\"token string single-quoted-string\">'books'</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">array</span><span class=\"token punctuation\">(</span>\n            <span class=\"token string single-quoted-string\">'content_splited'</span>\n        <span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></span></code></pre></div>\n<p>すでにレコードが存在している場合、<br>\n追加した分かち書き用カラムにcontentをパースした結果を足して更新する必要があるかと思いますが、この記事では本筋から外れるため割愛しています。</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">$ php oil r migrate\nPerformed migrations <span class=\"token keyword\">for</span> app:default:\n008_add_content_splited_to_books</code></pre></div>\n<p>マイグレーションを実行して動けばOKです。</p>\n<h2 id=\"分かち書きオブザーバを定義\" style=\"position:relative;\"><a href=\"#%E5%88%86%E3%81%8B%E3%81%A1%E6%9B%B8%E3%81%8D%E3%82%AA%E3%83%96%E3%82%B6%E3%83%BC%E3%83%90%E3%82%92%E5%AE%9A%E7%BE%A9\" aria-label=\"分かち書きオブザーバを定義 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>分かち書きオブザーバを定義</h2>\n<p><code>books</code>テーブルを扱う<code>Model_Book</code>クラスを作成します。<br>\n他のテーブルとJOINしたり何だりが楽なので、後々を考えて<code>Orm\\Model</code>を継承して実装します。</p>\n<p>content_splitedは検索用のメタ情報のようなものなので、モデル内部で黙字的に更新されるべきです。<br>\nコントローラがその存在を気にしなくて良いように内部で完結させます。</p>\n<p>DBへの <strong>INSERT前</strong> と <strong>UPDATE前</strong> に、<code>content</code>カラムのデータを分かち書きして、<br>\n<code>content_splited</code>カラムへ代入するオブザーバを作成します。</p>\n<p>モデルの定義としてはこんなイメージです。</p>\n<div class=\"gatsby-highlight\" data-language=\"php\"><pre class=\"language-php\"><code class=\"language-php\"><span class=\"token php language-php\"><span class=\"token delimiter important\">&lt;?php</span>\n\n<span class=\"token keyword\">class</span> <span class=\"token class-name-definition class-name\">Model_Book</span> <span class=\"token keyword\">extends</span> <span class=\"token class-name class-name-fully-qualified\"><span class=\"token punctuation\">\\</span>Orm<span class=\"token punctuation\">\\</span>Model</span>\n<span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> <span class=\"token constant\">DROP_WORD_LENGTH</span> <span class=\"token operator\">=</span> <span class=\"token number\">2</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">protected</span> <span class=\"token keyword\">static</span> <span class=\"token variable\">$_properties</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">array</span><span class=\"token punctuation\">(</span>\n        <span class=\"token string single-quoted-string\">'id'</span><span class=\"token punctuation\">,</span>\n        <span class=\"token string single-quoted-string\">'title'</span><span class=\"token punctuation\">,</span>\n        <span class=\"token string single-quoted-string\">'content'</span><span class=\"token punctuation\">,</span>\n        <span class=\"token string single-quoted-string\">'content_splited'</span><span class=\"token punctuation\">,</span>\n        <span class=\"token string single-quoted-string\">'created_at'</span><span class=\"token punctuation\">,</span>\n        <span class=\"token string single-quoted-string\">'updated_at'</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">protected</span> <span class=\"token keyword\">static</span> <span class=\"token variable\">$_observers</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">array</span><span class=\"token punctuation\">(</span>\n        <span class=\"token string single-quoted-string\">'Orm\\Observer_CreatedAt'</span> <span class=\"token operator\">=></span> <span class=\"token keyword\">array</span><span class=\"token punctuation\">(</span>\n            <span class=\"token string single-quoted-string\">'events'</span> <span class=\"token operator\">=></span> <span class=\"token keyword\">array</span><span class=\"token punctuation\">(</span><span class=\"token string single-quoted-string\">'before_insert'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n            <span class=\"token string single-quoted-string\">'mysql_timestamp'</span> <span class=\"token operator\">=></span> <span class=\"token constant boolean\">true</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        <span class=\"token string single-quoted-string\">'Orm\\Observer_UpdatedAt'</span> <span class=\"token operator\">=></span> <span class=\"token keyword\">array</span><span class=\"token punctuation\">(</span>\n            <span class=\"token string single-quoted-string\">'events'</span> <span class=\"token operator\">=></span> <span class=\"token keyword\">array</span><span class=\"token punctuation\">(</span><span class=\"token string single-quoted-string\">'before_insert'</span><span class=\"token punctuation\">,</span> <span class=\"token string single-quoted-string\">'before_update'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n            <span class=\"token string single-quoted-string\">'mysql_timestamp'</span> <span class=\"token operator\">=></span> <span class=\"token constant boolean\">true</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n        <span class=\"token string single-quoted-string\">'Model\\Observer\\Wakati'</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">array</span><span class=\"token punctuation\">(</span>\n            <span class=\"token string single-quoted-string\">'events'</span>           <span class=\"token operator\">=></span> <span class=\"token keyword\">array</span><span class=\"token punctuation\">(</span><span class=\"token string single-quoted-string\">'before_insert'</span><span class=\"token punctuation\">,</span> <span class=\"token string single-quoted-string\">'before_update'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n            <span class=\"token string single-quoted-string\">'wakati_from'</span>      <span class=\"token operator\">=></span> <span class=\"token string single-quoted-string\">'content'</span><span class=\"token punctuation\">,</span>\n            <span class=\"token string single-quoted-string\">'wakati_to'</span>        <span class=\"token operator\">=></span> <span class=\"token string single-quoted-string\">'content_splited'</span><span class=\"token punctuation\">,</span>\n            <span class=\"token string single-quoted-string\">'drop_word_length'</span> <span class=\"token operator\">=></span> <span class=\"token keyword static-context\">self</span><span class=\"token operator\">::</span><span class=\"token constant\">DROP_WORD_LENGTH</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></span></code></pre></div>\n<p>オブザーバのコードはやや長くなるのでgistに上げました。<br>\n環境構築時の設定で、2文字未満の単語は検索する際に無視する設定にしているので、2文字に満たない単語は保存しない処理が入っています。</p>\n<blockquote>\n<p>FuelPHPでMecabを使用して分かち書きするオブザーバ<br>\n<a href=\"https://gist.github.com/Leko/6c98685bdb048b949392#file-wakati-php\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">https://gist.github.com/Leko/6c98685bdb048b949392#file-wakati-php</a></p>\n<p>Creating – Observers – Orm Package – FuelPHP ドキュメント <a href=\"http://fuelphp.jp/docs/1.7/packages/orm/observers/creating.html\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">http://fuelphp.jp/docs/1.7/packages/orm/observers/creating.html</a></p>\n</blockquote>\n<h2 id=\"分かち書きした結果を保存する\" style=\"position:relative;\"><a href=\"#%E5%88%86%E3%81%8B%E3%81%A1%E6%9B%B8%E3%81%8D%E3%81%97%E3%81%9F%E7%B5%90%E6%9E%9C%E3%82%92%E4%BF%9D%E5%AD%98%E3%81%99%E3%82%8B\" aria-label=\"分かち書きした結果を保存する permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>分かち書きした結果を保存する</h2>\n<p>このオブザーバを使用することで、<code>content</code>プロパティに文章を指定して更新すれば、勝手に分かち書きした結果が<code>content_splited</code>に格納されるようになります。</p>\n<div class=\"gatsby-highlight\" data-language=\"php\"><pre class=\"language-php\"><code class=\"language-php\"><span class=\"token variable\">$book</span> <span class=\"token operator\">=</span> <span class=\"token class-name static-context\">Model_Book</span><span class=\"token operator\">::</span><span class=\"token function\">forge</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token comment\">// 青空文庫</span>\n<span class=\"token comment\">// http://www.aozora.gr.jp/cards/000535/files/3612_20811.html</span>\n<span class=\"token variable\">$book</span><span class=\"token operator\">-></span><span class=\"token property\">title</span> <span class=\"token operator\">=</span> <span class=\"token string single-quoted-string\">'言いたい事と言わねばならない事と'</span><span class=\"token punctuation\">;</span>\n<span class=\"token variable\">$book</span><span class=\"token operator\">-></span><span class=\"token property\">content</span> <span class=\"token operator\">=</span> <span class=\"token string single-quoted-string\">'人動もすれば、私を以て、言いたいことを言うから、結局、幸福だとする。長いので略、本当は全部入れてます'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token variable\">$book</span><span class=\"token operator\">-></span><span class=\"token function\">save</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token variable\">$book</span><span class=\"token operator\">-></span><span class=\"token property\">content_splited</span><span class=\"token punctuation\">;</span> <span class=\"token comment\">// => 'すれ を以て 言い たい こと 言う から 結局 幸福...略'</span></code></pre></div>\n<p>こんな感じです。</p>\n<h2 id=\"全文検索用のメソッドを作成する\" style=\"position:relative;\"><a href=\"#%E5%85%A8%E6%96%87%E6%A4%9C%E7%B4%A2%E7%94%A8%E3%81%AE%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B\" aria-label=\"全文検索用のメソッドを作成する permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>全文検索用のメソッドを作成する</h2>\n<p>お待ちかねの本題です。全文検索のメソッドを実装します。</p>\n<p>全文検索は<code>MATCH(カラム名) AGAINST ('+ほげ +ふー +ばー' IN BOOLEAN MODE)</code>のように扱います。<br>\nとてもざっくりした説明なので、詳しい説明は公式のリファレンスを参照して下さい。</p>\n<p><code>Orm</code>パッケージのクエリビルダは涙がでるほどイケてないので色々と気に食わない箇所がありますが、<br>\n今回はサンプルなので動けばいいやということで。実装する際にはいい感じにメソッド化したほうが良いと思います。</p>\n<p><a href=\"https://gist.github.com/Leko/6c98685bdb048b949392#file-book-php-L33\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">先ほどのgist</a>に上がっている<code>similarBooks</code>メソッドがこれにあたります。</p>\n<p>本の内容（<code>content</code>カラム）まで検索キーワードに含めるとノイズが多すぎるので、タイトルだけを検索キーワードにしています。</p>\n<blockquote>\n<p>（言い訳）カラム名と検索用テキスト、ふるい落とす文字数を渡してwhereの中身を１個の<code>Database_Expression</code>のインスタンスとしてwhereに渡したかったのですが、<br>\nまだ理解が浅いのかそもそもできないのか、うまくいかなかったので汚い書き方になっています。</p>\n</blockquote>\n<h2 id=\"全文検索用のメソッドを作成する-1\" style=\"position:relative;\"><a href=\"#%E5%85%A8%E6%96%87%E6%A4%9C%E7%B4%A2%E7%94%A8%E3%81%AE%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B-1\" aria-label=\"全文検索用のメソッドを作成する 1 permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>全文検索用のメソッドを作成する</h2>\n<p><a href=\"http://www.aozora.gr.jp/cards/000535/files/3612_20811.html\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">先ほどの青空文庫の文章</a>を入れて、例えば</p>\n<p>「幸福で愉快な陸軍が飛行機を奪われた」</p>\n<p>など本文中に直接は出てこないが本文中の単語を含んだ文で検索をかけると、検索に引っかかります。</p>\n<div class=\"gatsby-highlight\" data-language=\"php\"><pre class=\"language-php\"><code class=\"language-php\"><span class=\"token variable\">$book</span> <span class=\"token operator\">=</span> <span class=\"token class-name static-context\">Model_Book</span><span class=\"token operator\">::</span><span class=\"token function\">forge</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n<span class=\"token variable\">$book</span><span class=\"token operator\">-></span><span class=\"token property\">title</span> <span class=\"token operator\">=</span> <span class=\"token string single-quoted-string\">'幸福で愉快な陸軍が飛行機を奪われた'</span>\n<span class=\"token variable\">$book</span><span class=\"token operator\">-></span><span class=\"token function\">similarBooks</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>  <span class=\"token comment\">// => 青空文庫のレコードがマッチする</span></code></pre></div>\n<h2 id=\"まとめ\" style=\"position:relative;\"><a href=\"#%E3%81%BE%E3%81%A8%E3%82%81\" aria-label=\"まとめ permalink\" class=\"autolink-header before\"><svg aria-hidden=\"true\" focusable=\"false\" height=\"16\" version=\"1.1\" viewBox=\"0 0 16 16\" width=\"16\"><path fill-rule=\"evenodd\" d=\"M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z\"></path></svg></a>まとめ</h2>\n<p>駆け足で説明していきましたが、いかがでしたでしょうか。<br>\n「全文検索」って言葉は知っていたけど実装したことはなかったので、「こんな言葉で引っかかるのか！ すげえ！」とデバッグしながら一人で盛り上がってました。</p>\n<p><code>mecab</code>の説明を一切出さずモデルの裏側に隠してしまったのですが、<br>\nせっかく環境を作ったので、次は<code>mecab</code>と係り受け解析のライブラリとか使って文章の要約でもしてみようかなと思います。</p>\n<ol>\n  <li id=\"fn-754:1\">\n    <a href=\"http://dev.mysql.com/doc/refman/5.6/en/fulltext-restrictions.html\">12.9.5 Full-Text Restrictions</a> <a href=\"#fnref-754:1\" rev=\"footnote\">↩</a>\n  </li>\n  <li id=\"fn-754:2\">\n    <a href=\"http://y-ken.hatenablog.com/entry/mysql-casual-talks-vol4-innodb-fts\">MySQL-5.6.4からの新機能「InnoDB FullText Search」を用いた全文検索エンジンのベンチマークLTをしました。#mysqlcasual</a> <a href=\"#fnref-754:2\" rev=\"footnote\">↩</a>\n  </li>\n</ol>  ","timeToRead":10,"frontmatter":{"title":"FuelPHPでInnoDBの全文検索を利用してみる","tags":["PHP","FuelPHP","MySQL"],"date":"February 19, 2015","featuredImage":null}}},"pageContext":{"slug":"/how-to-implement-fulltext-search-with-fuelphp/","previous":{"fields":{"slug":"/create-git-hook-to-develop-modern-php/"},"frontmatter":{"title":"PHPで開発するためのgit hookを作った","tags":["Git","PHP"]}},"next":{"fields":{"slug":"/how-to-test-ie-with-karma/"},"frontmatter":{"title":"Karmaを使ってIEのテストをMacから行う","tags":["ES5","Jasmine","JavaScript","Karma"]}}}},
    "staticQueryHashes": ["2585454260","2954598359"]}