2012年6月6日水曜日

郵便番号検索を作る─4.都道府県/市区町村リストを作成

今まで、最初ということで郵便番号から住所リストを出すっていうように作ってたけど、それだと郵便番号検索としては意味をなさないんで、これからは住所から郵便番号を検索できる機能を作っていく。

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

別にぼくしか使わないからMysqlでフルテーブルスキャンさせてリストを作成する、というのでもいいんだけど、あんまり勉強にはならないんで以下の方法でリストを作成するようにする。

JISコードを利用する
今回JISコードを文字列型で登録した。このJISコードは、左の2桁が都道府県、残りの3桁で市区町村を表してるらしいので、ここにインデックスを張ってリストを作る。

都道府県は新しいテーブルを作成
stateフィールドにインデックスを張った方が断然やりやすいし管理も簡単だけど、他のテーブルを使用するってことをやりたいから(あえて面倒な)この方法で都道府県リストは作成します。

なお、今回はテーブルは作成してもhasManybelongsTohasOneなどのjoin系は使わない。 それはまた別の機会に。

postal_codesテーブルにインデックスを張る

実のところ、こういう場合はstatecity複合インデックスを張る方が、データは膨らんでもカバリングインデックスが使える分数倍早くなるんだけどね、多分。でも今回は他のテーブルも使うということで。

mysql> alter table postal_codes add index jiscode (jiscode);
Query OK, 124650 rows affected (9.73 sec)
Records: 124650  Duplicates: 0  Warnings: 0

続いて、都道府県だけのテーブルを作成する。別にjoinもしないテーブルだし、毎回フルテーブルスキャンになってInnodbのメリットがそんなにないからMyiSamで作成する。

mysql> create table if not exists postal_code_states ( 
    -> id varchar(2) collate utf8_bin not null ,
    -> state varchar(10) collate utf8_bin not null ,
    -> primary key (id)
    -> )Engine=Myisam default character set utf8 collate utf8_bin;
Query OK, 0 rows affected (0.12 sec)

#データを挿入
mysql> insert into postal_code_states (id , state) select left(jiscode , 2) as id , state from postal_codes group by jis ;
Query OK, 47 rows affected (0.44 sec)
Records: 47  Duplicates: 0  Warnings: 0

#データを確認
mysql> select * from postal_code_states limit 2;
+-----+-----------+
| id  | state     |
+-----+-----------+
| 01  | 北海道 |
| 02  | 青森県 |
+-----+-----------+
2 rows in set (0.00 sec)

うん、多分うまくできてる気がする。

モデルを作成

これはとても簡単。命名規約で単数形にするのだけ忘れずに。

//Model/PostalCodeState.php
<?php
    class PostalCodeState extends AppModel{ }
?>
実装していく

作成手順としては

1:listsアクションに引数が何もなければ都道府県リストを出す
2:listsアクションに引数1があれば市区町村リストを出す
3:listsアクションに引数1、2があれば詳細リストを出す
4:詳細リストからはaddressへとリンクを飛ばす

という感じで実装します。

コントローラーでPostalCodeStateモデルを使用できるように

命名規約からはずれるモデル、あるいは複数モデルを使用する場合、$uses変数内にモデルを指定する。
(なお、モデルを指定した場合は命名規約に沿ってるモデル名も書かないといけなくなるみたい)

//Controller/PostalCodesController.php内
 class PostalCodesController extends AppController {
    //使用モデルを指定する
    public $uses = array('PostalCode','PostalCodeState');
 }

これで使用できるようになる。

listsアクションを作成する

ちなみに、listという名前でアクションをつくろうとすると、PHP関数の予約語とかぶるから当然エラーになる(間違えて最初しちゃった。list()もそうだったんだね)。

//Controller/PostalCodesController.php内
  public function lists($state = null , $jis = null){
    //パン屑リストを生成する為の
    $crumb[] = array('インデックス',array('action'=>'index'));
    //$stateがなければ都道府県リストを出す
    if(!isset($state)){
      $result = $this->PostalCodeState->find('all',array('order'=>'id'));
      $crumb[] = array('都道府県リスト',null);
      $this->set(compact('result','crumb','state'));
      $this->render("lists");
    }
  }

次いで、lists.ctpを作成する。

//View/PostalCodes/lists.ctp内
<?php
    foreach($crumb as $row){
    $this->Html->addCrumb($row[0] , $row[1]);
    }
    print $this->Html->getCrumbs('>');
?>
<hr>
<ul>
<?php foreach($result as $row): ?>
<?php if(empty($state)): ?>
<li><?php print $this->Html->link($row['PostalCodeState']['state'] , array($row['PostalCodeState']['id'])); ?></li>
<?php endif; ?>
<?php endforeach; ?>
</ul>

ここまでは簡単ですね。

同じような感じでどんどん作成

あとは作り方は殆ど同じだから、全部作っちゃいます。なお、下記みたいにアクション名の左側にアンダーバー(_)を入れると、URLからダイレクトにアクセスできなくできる。
内部処理だけで使うメソッドにはそうした方がいいかも。

//Controller/PostalCodesController.php内
   public function lists($state = null , $jis = null){
       $crumb[] = array('インデックス',array('action'=>'index'));
       if(!isset($state)){
         $result = $this->PostalCodeState->find('all',array('order'=>'id'));
         $crumb[] = array('都道府県リスト',null);
         $this->set(compact('result','crumb','state'));
         $this->render('lists');
       }else{
         $crumb[] = array('都道府県リスト',array('action'=>'lists'));
         $this->_lists2($state , $jis , $crumb);
       }
   }
   //市区町村リスト
   public function _lists2($state , $jis , $crumb){
       $states = $this->PostalCodeState->read('state',$state);
       if(empty($states)){
         $this->Session->setFlash('そんな都道府県は存在しません');
         $this->redirect('lists');
       }
       if(!isset($jis)){
         $crumb[] = array($states['PostalCodeState']['state'],null);
         $result = $this->PostalCode->find('all',array('conditions'=>array('jiscode like'=>$state . '%') , 'order'=>'jiscode' , 'group'=>'jiscode'));
         $this->set(compact('result','crumb','state','jis'));
         $this->render('lists');
       }else{
         $crumb[] = array($states['PostalCodeState']['state'],array('action'=>'lists',$state));
         $this->_lists3($state , $jis , $crumb);
       }
   }
   
   //詳細リスト
   public function _lists3($state , $jis , $crumb){
       $cities = $this->PostalCode->findByJiscode($jis);
       if(empty($cities)){
         $this->Session->setFlash('そんな市区町村はありません');
         $this->redirect(array('action'=>'lists' , $state));
       }
       $crumb[] = array($cities['PostalCode']['city'],null);
       $result = $this->PostalCode->find('all',array('conditions'=>array('jiscode'=>$jis)));
       $this->set(compact('result','crumb','state','jis'));
       $this->render('lists');
   }
//View/PostalCode/lists.ctp内
<?php
    foreach($crumb as $row){
    $this->Html->addCrumb($row[0] , $row[1]);
    }
    print $this->Html->getCrumbs('>');
?>
<hr>
<ul>
<?php foreach($result as $row): ?>
<?php if(empty($state)): ?>
<li><?php print $this->Html->link($row['PostalCodeState']['state'] , array($row['PostalCodeState']['id'])); ?></li>
<?php elseif(empty($jis)): ?>
<li><?php print $this->Html->link($row['PostalCode']['city'] , array($state , $row['PostalCode']['jiscode'])); ?></li>
<?php else: ?>
<li><?php print $this->Html->link($row['PostalCode']['city'].$row['PostalCode']['street'] , array('action'=>'address' , $row['PostalCode']['zipcode'])); ?>
(<?php print $row['PostalCode']['zipcode']; ?>)</li>
<?php endif; ?>
<?php endforeach; ?>
</ul>

簡単ですね。
最後に、今回初めて使った$this->render()は、アクション名を指定することでそのアクションに合ったctpファイルをレンダリングしてくれる。(今回の例だと、どれもlists.ctpビューを描写する)

$this->render('アクション名');

今回は全部をlists.ctpで描写するようにしたけど、これを使うことでURLは変更せずにビューページを分けたりすることだってできちゃう。

0 件のコメント:

コメントを投稿