以前にちょろっと申し上げましたが、検索部分に関しては改良の余地があります。
ただ、普通にやっててもどうしようもないんですが、変則的な手段を用いれば解決可能な気がしたので実装してみようか、と思い立ったのです。
その答えが
検索用のカラムを用意するということです。
どういうことかというと、「・」とか、そもそも「ァィゥェォ」などの紛らわしい表記を全て消し去ったり置き換えたりした文字列のカラムを用いればいけるのではと。
つまり、こういう厄介な輩どもを置換する関数を用意し、検索する時もこいつを通して、置換後のカラムを検索するようにすれば全ての問題は解決しますね。
こうすることで、表示する名前は「◯◯・××」であっても、実際に検索しているのは「◯◯××」であり、しかもこれはユーザーの目に見えることはない、という理想的なものに近づくと思われます。
そして、もう一つ画期的なことを思いつきました。
自動マークアップです。
よくよく考えてみれば、マークアップの必要に迫られるものって
大概がカタカナのみなんですよね。
つまり、カタカナのみのものだけを正規表現で抜き出してチェックしていけば、マークアップを人力でやる必要はないのではと。
もちろん「・」の有り無しなどでつまづくのですが、そこで先程の関数を適用し、検索用のカラムを別途用意していたら・・・・・・?
LIKE検索が大量に投げられるのでスロークエリにならないか心配ですが
(まぁインデックス付けてるし大丈夫だと思うんですけど)、管理画面のみの実装なのでユーザーにしてみれば関係ないし大丈夫かなと思います。
ただ、不要なマークアップが発生する可能性があるので、除外用の辞書なども用意しておいてもいいですかね。カクテル名とかぶりそうですし。そういった場合は明示的にマークアップすることで区別を可能としたいところですね。
で、実装してみました。
function auto_markup(string $str=null){
//自動でマークアップを施す。
if(empty($str)){return $str;}//空ならばそのまま返す
$converted= convert_at_id($str);//まず明示的なマークアップを全て変換する。
//
//カタカナのみの羅列はマークアップできる可能性があるため検索。
$result=preg_replace_callback(
'/[ァ-ヶ][ァ-ヶ・ー]+/u',
function ($matches) {
$search=$matches[0];//カタカナの羅列が見つかった
//逐一検索してみましょう。
//まずはカテゴリ
$id=(new M_categoryDAO())->where("name_search", "=", convert_to_name_search($search))->value("id");
if($id!=null){return "@p".$id."@";}
//次にカクテル
$id=(new T_cocktailsDAO())->where("name_search", "=", convert_to_name_search($search))->value("id");
if($id!=null){return "@c".$id."@";}
//次にミドルカテゴリ
$id=(new M_middleDAO())->where("name_search", "=", convert_to_name_search($search))->value("id");
if($id!=null){return "@m".$id."@";}
//最後に銘柄
$id=(new M_brandDAO())->where("name_search", "=", convert_to_name_search($search))->value("id");
if($id!=null){return "@b".$id."@";}
return $search;//見つからなければそのまま
},
//メインカテゴリ
preg_replace_callback(
'/生クリーム|砂糖|紹興酒|梅酒|日本酒|焼酎|番茶|食塩|卵/u',
function ($matches) {
$id=(new M_categoryDAO())->where("name_search", "=", convert_to_name_search($matches[0]))->value("id");
return "@p".$id."@";
},
//ミドルカテゴリ
preg_replace_callback(
'/(芋|麦|米|甲類)焼酎|(赤|白)ワイン|卵黄|卵白/u',
function ($matches) {
$id=(new M_middleDAO())->where("name_search", "=", convert_to_name_search($matches[0]))->value("id");
return "@m".$id."@";
},
$converted))
);
return $result;
}
実装の中身はこんな感じです。
こいつをUtil関数にしているのは明らかな失敗です。
こういう再帰的な検索は
preg_replace_callback()という関数があるようで、こいつを使えばスマートに実装できます。
convert_to_name_search(string)が、さっき言った
「"・"を消して検索用カラムにぶち込める形に変換する関数」ですね。こいつを元に検索することで、カタカナのみの表記であれば多少揺れてもヒットすることになります。
そこでID形式に書き換えることができれば、以降、DBに登録された名前で表示されるようになります。
この正規表現、非常に悩ましく半日溶かしたんですけど、上のような形でいいらしいです。UTF-8だと他のSJISなんかと違ってちょっと書き方が違ってるみたいで。小さいァが最初に来るんですね。
あと、この書き方だと、例えば「アーモンド」とかだと「アー」「アーモ」「アーモン」...ってヒットするんじゃないかって心配してたんですけど、そうじゃないみたいです。
その気になれば漢字を含んだ自動マークアップも一応、やろうと思えばできます。要するにDBの中身全部取ってきてforeachで回しながら検索する、っていう形ですね。
ただ、どう考えてもコストやばそうで、処理時間が心配になってくるのでやめました。まぁカタカナだけ変換できれば上出来でしょう。あることさえ知ってたら自分でマークアップすれば済む話ではありますからね。それに「水」とか「氷」とかいちいちヒットされても困るものもありますし。
とはいえ「生クリーム」ぐらいは自動で変換して欲しいなぁと思ったので、無理矢理後に書き加えてます。さすがに銘柄まではサポートする気ありません(笑)
ミドルカテゴリを先に変換することで、焼酎とか卵とかが中途半端に変換されるのを防いでいます。
まぁ、プログラムを知らない人からすると上から順に実行されるように見えちゃうんですけどね(笑)
そもそもこの記事見てない気がするけど
ちなみに、正規表現の後ろに「u」とかくっついてますが、調べてみると
強制的にUTF-8として処理するっていう修飾子らしいです。全部UTF-8として書いているつもりなので、バグを防ぐためにもつけておくのが正解でしょうね。
ということで、ユーザー側には一切関係ありませんが、この自動マークアップ機能のお陰で
更新作業が異常にラクになったので、嬉しくなって記事にしました(笑)
このDBの理想形としては、あるカクテルから
芋づる式に他のカクテルにたどり着けるという、「知識の集約」を目指しているので、こういう手間は惜しみたくありません。
今はまだまだ数が少ないのですが、例えば何かのリキュールや銘柄のページにたどり着いた時
「これを使ったカクテル」という項目があるのもその意識の現れです。
世の中には色んなカクテルのデータベースがありますが、この親切さは他のデータベースにはない魅力になると僕は信じています(笑)
まぁもっと他にも意識してるところはあるんですけどね。自分の飲んだことないカクテルは敢えて「あいまいな表現」にしてたりね。
まずいカクテルはまずいと言いたいし
(でも、気になる人は飲んでみて笑、という意味でもある)、全部を美味しそうに表現するのは情報の信憑性に欠けるから、あまり言いたくないなぁって。
頑張って作ってるんだし、1年後ぐらいに芽が出てくれるといいなぁ・・・・・・。本格的に稼働するためにはいくつぐらいデータがあればいいんだろうね。
まぁまずは果物を買える財力を身に着けてからだな・・・・・・。
今日のおまけコーナー。
数を増やさないと意味がないので頑張って撮ってますけど、なかなかしんどいなぁほんと(笑)