2012年5月16日水曜日

郵便番号検索を作る─2.簡単なページを作成

テーブルに関しては前々回に作成&郵便番号データを放り込んだ。ということで、今回からは実際に作成していってみる。

今回やることは以下の通り。

1.コントローラとアクションを作成(index()とaddress()アクション)
2.Formヘルパーを使ってindex.ctpに郵便番号での検索ページを作成
3.モデルのbeforeFind()、afterFind()を使って郵便番号のデータを加工
4.郵便番号は前方一致で検索できるようにする
5.address.ctpにて郵便番号からの住所結果を出力
6.検索結果が存在しない場合はindex()へと戻す


最終的には住所などから郵便番号を出すという機能を実装するつもりだけど、まずは郵便番号を入力したら住所を出す、という機能を作り、その後機能を付け足していくという形で作成していきます。

コントローラーを作成

まずは、どうなるかわからないので空のアクションだけを作成する。

//app/Controller/PostalCodesController
<?php
   class PostalCodesController extends AppController{
        public $helpers = array('Html','Form','Session'); //*
        public function index(){
        
        }
 
        public function address(){

        }
   }
?>
public $helpersの説明はこちら
ビュー(index.ctp)を作成

コントローラーを作成したら、次はビューファイルを作成していく。
ひとまず、index.ctpを作成。ここではFormヘルパーを使って郵便番号入力フォームを作成する。

//View/PostalCodes/index.ctp
<H1>郵便番号検索</H1>
<?php
    //フォームを作成。
    //命名規約に沿ってたら特に引数は必要ないっぽい。
    print $this->Form->create();

    //フォーム要素を作成。下記の場合はテキストボックスが作成される。
    print $this->Form->input('zipcode' , array('label'=>'郵便番号'));

    //フォームを終了する。
    //下のように文字列を引数で渡したら送信ボタンが作成される。
    print $this->Form->end('送信');
?>

アクセスしてみる。
http://localhost/cakePHPfolder/postal_codes/

簡単なものだね。

Formヘルパーの簡単なまとめはここに多少まとめました
index()アクションをいじる

上の作成の仕方の場合、$this->Form->create()のデフォルトによりPOSTデータがそのままindex()へと送られる。
というわけで、index()内を編集してポストデータが送られたら送られたデータを解析し、別の動作をするよう編集する。

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

本当は前方一致で検索できるようにしたいんだけど、まずは7桁での検索から作成してみます。
なお、上記の説明は以下の通り。

$this->request->is('post')

今POSTリクエストかどうかをこれで判断する。なお、$this->request->is()では以下の判別ができるとても便利な関数。

$this->request->is('文字');の主な一覧
postPOSTリクエストか
getGETリクエストか
putPUTリクエストか
deleteDeleteリクエストか
ajaxAjaxリクエストか
mobile携帯からのリクエストか

なお、putに関しては、ビュー出力時に$this->request->dataの中にデータが設定されてあった状態で、フォームがPOST送信された場合になる。
deleteは$this->Form->create()の中で'type'=>'delete'と設定した場合になるっぽい。
(putも'type'=>'put'とすることでそうなる)。

$this->request->data

POSTでの送信内容はこの中に格納される。
なお、上記では配列として取り出しているが、関数で取り出すこともできる。

 $data = $this->request->data('PostalCode.zipcode');

最初のだと、たとえば$this->request->data['PostalCode']['zipcode']が存在しなかった場合(空ではなくそもそもその配列のキーが存在しなかった場合)エラーが起きるから上のように関数で呼び出して存在するかどうか確かめる方がいいかも。

また、この中にデータを格納することで、フォームのデフォルト値を設定することもできる。
(指定の仕方はFormヘルパーの使い方の部分に記載しました)

$this->Session->setFlash()

1回かぎりのメッセージを入力できるみたい。上記の場合なら、もし正規表現のルールに沿ってない場合、

入力が正しくありません。郵便番号は7桁の半角数字か***-****形式で入力して下さい

みたいに出力される。

$this->redirect()

その名前の通り、リダイレクトしてくれる。上記の場合なら、http://localhost/cakephpFolder/postal_codes/address/$dataにリダイレクトされる。
(このURLの場合、cakePHP内で$dataがaddress()アクションの第一引数として渡される)

address()アクションとaddres.ctpファイルを作る

ここでは渡された引数を元に、郵便番号を検索してデータを吐き出す。また、データがなかったらindex()へと戻すように指定する。

(今は7桁でしか郵便番号検索できないようにしてるけど、次回複数検索もできるようにするつもりだから、それを想定して複数の検索結果を表示できるようにしてます)

//Controller/PostalCodesController.php内
public function address($zipcode = 0){
  $options = array('conditions'=>array('zipcode'=>$zipcode));
  if(!$result = $this->PostalCode->find('all',$options)){
    $this->Session->setFlash("ヒットしませんでした");
    $this->redirect($this->referer(array('action'=>'index')));
  }else{
    $this->set(compact('result'));
  }
}



//View/PostalCodes/address.ctp内
<?php 
    $count = 0; 
    $cause = array(0=>'変更なし',1=>'市政・区政・町政・分区・政令指定都市施行',2=>'住居表示の実施',3=>'区画整理',4=>'郵便区調整等',5=>'訂正',6=>'廃止');
    $this->Html->addCrumb('検索',array('action'=>'index'));
    $this->Html->addCrumb('検索結果');
?>
<?php print $this->Html->getCrumbs(">"); ?>
<hr>
<H1>検索結果</H1>
<?php foreach($result as $rows): ?>
【<?php print ++$count; ?>】
<table border='1' style='margin-bottom:20px; width:700px;'>
    <tr><th width='200'>郵便番号</th><td><b style="color:#292999;"><?php print $rows['PostalCode']['zipcode']; ?></b></td></tr>
    <tr><th width='200'>JISコード</th><td><?php print $rows['PostalCode']['jiscode']; ?></td></tr>
    <tr><th width='200'>都道府県</th><td><?php print $rows['PostalCode']['state']; ?>(<?php print $rows['PostalCode']['state_kana']; ?>)</td></tr>
    <tr><th width='200'>市区町村</th><td><?php print $rows['PostalCode']['city']; ?>(<?php print $rows['PostalCode']['city_kana']; ?>)</td></tr>
    <tr><th width='200'>町域名</th><td><?php print $rows['PostalCode']['street']; ?>(<?php print $rows['PostalCode']['street_kana']; ?>)</td></tr>
    <tr><th width='200'>変更</th><td><?php print $cause[$rows['PostalCode']['cause']]; ?></td></tr>
</table>
<?php endforeach; ?>
<?php print $this->Html->link('戻る',array('action'=>'index')); ?>

また、初めて使うのをひとつひとつ紹介していく。

$this->PostalCode->find('all',$options)

これが、モデルのデータ(つまりはMysqlのデータ)を取り出すcakePHP関数となる。実際は

$this->モデル名->find($type , $options)

という書き方になる。
これは、相当色んなことができるっぽいので詳細はFormヘルパー同様また別の機会にまとめるとして、今は$options = array('conditions'=>array('zipcode'=>$zipcode));にて郵便番号を指定し、'all'で検索結果を全部出力するということだけ。

$this->referer(array('action'=>'index'))

これもかなり使える関数。HTTP_REFERERが読み取れた場合はそれを返し、読み取れなかった場合は中の配列を返す。
これを使うことで、あちこちのアクションからこのaddressにアクセスしたとしても、それぞれのアクションへと簡単に返すことができる。

$this->set(compact('result'));

一番使う関数になるのかも。Viewページで使う変数をこの関数で渡す。
この場合は$resultをViewページへと渡すことができる。
(なお、compactはPHP標準関数です。念のため)

$this->Html->addCrumb('検索',array('action'=>'index'));
$this->Html->getCrumbs(">");

これも結構便利そうな関数。トピックパス(俗に言うパンくずリスト)を作成してくれる。

$this->Html->link('戻る',array('action'=>'index'));

直感的に解るけど、リンクを作成できる。これも、また別の機会に詳しい使い方をまとめようかな。

渡される郵便番号データを検索前と検索後に加工

さて、これで郵便番号で住所が検索できる、わけじゃない。
というのも、面倒にも今回郵便番号データをdecimal型にしちゃってるからだ。だから、次にはモデル内にbeforeFind()afterFind()関数を作り、その中で郵便番号データを加工する。
この2つの関数は、作成しておくと検索開始時と検索後の出力時にいろいろな指示を与えられる。

//Model/PostalCode.php内
   public function beforeFind($queryData){
     if(!empty($queryData['conditions']['zipcode']) and preg_match("/^(\d{3})\-?(\d{4})$/i",$queryData['conditions']['zipcode'],$match)){
       $queryData['conditions']['zipcode'] = (int)($match[1].$match[2]) / 10000000;
     }
     return $queryData;
   }

   public function afterFind($result){
     foreach($result as $key => $row){
        if(!empty($row[$this->alias]['zipcode'])
            && preg_match("/^(\d{3})(\d{4})$/i",str_pad($row[$this->alias]['zipcode'] * 10000000 , 7 , '0' , STR_PAD_LEFT), $match)){
         $result[$key][$this->alias]['zipcode'] = $match[1] . '-' . $match[2];
        }
     }
     return $result;
   }
[追記]$this->aliasについて

(2012/05/17追記)
モデル内またはAppModel内で自身のモデルを記載する際、直接記載するのではなくこの変数を使うようにした方がいいみたい。
モデルの名前を別のに指定していた場合でも、これだとエラーが起きなくなる。


これで、$this->PostalCode->find()時にconditions配列の中にzipcodeが指定されていたら小数点の形へと変形してくれ、データを取り出す時には郵便番号の形へと変換してくれる。

検索してみる

さて、これで内部的に変更してくれ、こっちは郵便番号を郵便番号のフォーマットのまま検索できるようになった。
というわけで、東京都中央区(103-0001)を検索してみる。

うまくいった\(^o^)/


公開するかぎりはあまりいい加減なことは書けないし、今回作成する際に相当いろいろと調べて(それがブログを作った目的でもあるんだけど)かなり時間がかかってしまったが、なんとかcakePHP2.1で簡単な検索だけはできるようになった。
次回は、郵便番号を前方一致で検索できるように作り替えます。

本当は住所から郵便番号を検索することが目的だけど、それはまだもうちょっと先の話になりそう。

【参考リンク】
CookBook2.x(英語版)
FormHelper Class Info:
CakeRequest Class Info:

0 件のコメント:

コメントを投稿