2012年5月18日金曜日

郵便番号検索を作る─3.郵便番号前方一致で検索

前回までで郵便番号を入力したらその住所が表示されるところまで作った。
本当は住所から郵便番号を検索できるようにするのを目標としてるんだけど、もうちょっと今回のを拡張し、前方一致で検索できるように作り替えてみる。

というより、ある程度手順を先に書いておいた方がいいね。

■作成手順
・郵便番号データを作成
・郵便番号の全文一致でリストを出力
・郵便番号の前方一致でリストを出力←今回ココ
・都道府県、市区町村によるリストを作成
・住所(漢字、カナ、かな)で検索できるように
・郵便番号データに知人/友人を登録できるようにする
・レイアウトをカスタマイズする

こんな感じでいこっかな、と思う。というわけで、作成していきます。

$this->ModelName->findのまとめ

まず、作り出す前にfindについてちょこっとまとめた。この関数は、要はSelectクエリを発行し、cakePHP内で使いやすい配列として返してくれる関数だ。

$this->[ModelName]->find(string $type , array $params);

[ModelName]
モデルの名前をキャメル型で指定する。今回の場合だと$this->PostalCode->find()という形になる。

$type
どういう形で配列を出すか。デフォルトはfirstとなってる。

$typeの種類
first最初の1件だけ配列で返す(limit 1と同じ)
all検索結果を全て配列で返す
count検索の数を返す
listIDと$displayFieldで指定されたものだけ配列で返す*
neighborsデータの前の値と次の値を配列で返す
threadedデータをスレッドの配列型で返す

neighborsとかかなり使えそう。
$displayFieldに関しては、フィールド名にnameまたはtitleがあれば自動的に設定される。
そんなフィールドがない、または別で指定したい場合は以下のようにモデル内で指定する。

//モデル内
class Model extends AppModel{
    public $displayField = 'フィールド名';
}

$params
それぞれ検索条件、フィールド名、ソートなどを指定する。

主な$paramsの指定
conditions検索条件を配列で指定
fields返すフィールドを指定
(指定しなければ全部返す)
orderソートする
groupグループ化する
limit何件返すか指定
offsetlimitからのオフセット値を指定
page検索結果のページを指定
recursive複数テーブルを使用する際、どの階層まで追うかを指定
callbacksbeforeFind、afterFindを使用するか

limitpageの2つを使えば、ページングが容易にできるようになるみたい。詳しい使い方は使う時に調べよ。

conditionsは基本的には配列で'フィールド名'=>'値'という形で指定するが、値を配列にすると自動的にIN句になる。
またBETWEENや >、<、>=、<=を使う時にはフィールド名を特殊な形に変えるみたい。それらも使う時に書いていきます。

コントローラーを変更する

今回作成している郵便番号データは、郵便番号(zipcode)を文字列型ではなくあえて使いづらいdecimal型にしている。
ただ、そのおかげでBETWEEN句で前方一致を実装できる。

■送られた郵便番号が7桁
 →そのまま検索
■送られた郵便番号が6桁以下
 →桁数を計算し、それを元に範囲検索する。
 (例)103-00と送られた → 0.103000 ~ 0.103009を検索

というわけで、まずは前回作成したindex()アクションにて、6桁以下でもaddress()へ渡すように作り変える。

//Controller/PostalCodesController.php内
   public function index(){
     if($this->request->is('post')){
       $data = $this->request->data('PostalCode.zipcode');
       if(!preg_match("/([0-9]{3})\-?([0-9]{0,4})/i" , $data)){
          $this->Session->setFlash("入力が正しくありません。郵便番号は3桁以上の半角数字か***-****形式で入力して下さい");
       }else{
          $this->redirect(array('action'=>'address',$data));
       }
     }
   }

ついでに、前回$data = $this->request->data['PostalCode']['zipcode'];としてた部分を、エラーにならないようにする為に書き換えた。

モデルのbeforeFind()を書き換える

次に、beforeFind()内で郵便番号コードを検索できる形に加工するよう作り替える。

//Model/PostalCode.php内
   public function beforeFind($queryData){
      if(!empty($queryData['conditions']['zipcode'])
         && preg_match("/^(\d{3})\-?(\d{0,4})$/i",$queryData['conditions']['zipcode'],$match)){
         $zip_len = strlen($zipcode = (int)($match[1].$match[2]));
         if($zip_len < 7){ //数値が7桁未満の場合
            $from_zipcode = (int)($zipcode . "0") / pow(10 , $zip_len + 1);
            $to_zipcode = (int)($zipcode . "9") / pow(10 , $zip_len + 1);
            $queryData['conditions']['zipcode BETWEEN ? AND ?'] = array($from_zipcode   , $to_zipcode);
            unset($queryData['conditions']['zipcode']);
         }else{//それ以外
            $queryData['conditions']['zipcode'] = (int)($match[1].$match[2]) / 10000000;
         }
      }
   }

この'zipcode BETWEEN ? AND ?'というのが、BETWEENでの範囲検索の際に指定する方法となる。

'フィールド名 BETWEEN ? AND ?' = array('範囲1','範囲2');

値が7桁未満なら検索方法をBETWEENに変更し、7桁なら以前と同じ方法で検索する。
ちょっとコードが汚いなぁ。もうちょっと綺麗な作り方もある気がするけど、まあ多分動いてくれるでしょ。

実際に試してみる

さて、この2箇所を変更したら、もう前方一致検索できるようになってるはず。

うんうん、できてる。


というわけで、今回仕様を変更する際、cakePHPなら特定の部分をちょこちょこっと変えることで簡単に変えられることがわかった。
この程度のことなら、普通に作ってもやっぱりちょこちょこっといじることで簡単に変更できそうだけどね。
でも、それぞれの役割や動作がページやポジションによって明確になってることで、どこを変えたらいいかは他の人がいじる場合でもわかりやすそうかも、という印象は持った。
多分それがフレームワークってやつなんだろうね。

【参考リンク】
cakePHP2.x(英語版)──find

0 件のコメント:

コメントを投稿