九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、Laravel5.7
の新機能をLaravel 5.7 の新機能まとめで一通り触ってみたのでこの辺で以前から作っておきたかった、ある機能をつくることに時間を割くことにしました。
その機能とは、郵便番号検索です。
つまり、131-0045
と郵便番号を入れて検索すると東京都墨田区押上
というデータを取得できる機能ですね。
もちろん、JavaScriptにほんの少し設定を追加するだけで、この機能を実装できるajaxzip3という素晴らしいサービスがあることは知っています。(というかよく利用させていただいてます。ありがとうございます。m_ _m)
が、クライアントさんによっては「将来的になくなるかもしれないサービスは極力使わない」方針の方がいらっしゃるのも事実です。実際、ajaxzipは過去にGoogle CodeからGitHubへ移行していますし、Googleは特によくサービスを終了してしまうので、下手するとcdnなどもなくなってしまう可能性だってあるわけです。
そこで、今回は独自の郵便番号検索を作ってみることにします。ぜひみなさんも参考にしてみてくださいね。
【追記:2019.12.10】この記事の内容をベースとしてパッケージを公開しました。詳しくは時短!郵便番号から住所が検索できるパッケージを公開しましたをご覧ください!
目次
やりたいこと
冒頭部分でも書きましたが、以下のような流れを作ります。
- 郵便番号を<input type=”text”>に入力する
- ボタンをクリックするとAjax経由で郵便番号に対応する住所を検索する
- 住所データを取得し、住所入力ボックスで表示する
※ 開発環境はLaravel 5.7
+ nginx
+ PHP 7.2
です。
郵便番号検索をつくる
データをダウンロードする
郵便データは日本郵便のウェブサイトでCSVとしてダウンロードできます。今回は読み仮名データの促音・拗音を小書きで表記しないものの中にあるken_all.zip
という全国一括版のファイルをダウンロードしました。
ではこのzipファイルを展開してKEN_ALL.CSV
というファイルを取り出します。
そして、Laravelからアクセスできるよう、storage/csv/
内にコピーしておきましょう。
郵便データを保存するテーブルを作成する
CSVのままでは検索しにくいので、必要なデータは全てデータベース内に移動させます。では、テーブルの設計書「マイグレーション」を作っていきましょう。
どうせ後で必要になるので、以下のコマンドでマイグレーションと同時にモデルも作成しておきましょう。
php artisan make:model PostalCode -m
では、database/migrations
内にxxxx_xx_xx_xxxxxx_create_postal_codes_table.php
というファイルができている(作成時間によってファイル名は若干違います)ので、up()の中身を以下のように変更します。
public function up() { Schema::create('postal_codes', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('first_code')->index(); $table->unsignedInteger('last_code')->index(); $table->string('prefecture'); $table->string('city'); $table->string('address'); }); }
内容は、
- ID ・・・ オートインクリメントで連番
- first_code ・・・ 郵便番号の始め(3桁)の部分
- last_code ・・・ 郵便番号の後ろ(4桁)の部分
- prefecture ・・・ 都道府県名
- city ・・・ 市区町村
- address ・・・ それ移行の住所
となっています。
ちなみに、郵便データはマスターデータなので$table->timestamps();
は削除しています。(つまり、created_at
とupdated_at
を使わない)
そのため、PostalCode
でタイムスタンプを使わないように設定し、ついでにデータ追加時にエラーがでないよう$guarded
も追加しておきます。
<?php namespace App; use Illuminate\Database\Eloquent\Model; class PostalCode extends Model { public $timestamps = false; protected $guarded = ['id']; }
では、これでマイグレーションは完成しましたので、以下のコマンドを実行してテーブルを作成しましょう。
php artisan migrate
郵便データをDBへ移動させる
郵便データはSeederを作って格納してもいいのですが、先ほどの日本郵便のダウンロードページを見ると結構頻繁にデータ更新されているようでしたので、その都度更新がしやすいように独自のコマンドをつくります。
php artisan make:command ImportPostalCodeCommand
次に、app/Console/Commands/ImportPostalCodeCommand.php
を開いて、中身を以下のように変更します。変更、追加した場所は太字にしています。
<?php namespace App\Console\Commands; use Illuminate\Console\Command; class ImportPostalCodeCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'import:postal-code'; /** * The console command description. * * @var string */ protected $description = 'Import postal-code'; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); } /** * Execute the console command. * * @return mixed */ public function handle() { // テーブルを空にする \App\PostalCode::truncate(); // CSVファイルの文字コード変換 $csv_path = storage_path('app/csv/KEN_ALL.CSV'); $converted_csv_path = storage_path('app/csv/postal_code_utf8.csv'); file_put_contents( $converted_csv_path, mb_convert_encoding( file_get_contents($csv_path), 'UTF-8', 'SJIS-win' ) ); // CSVから郵便データを取得してDBへ保存 $file = new \SplFileObject($converted_csv_path); $file->setFlags(\SplFileObject::READ_CSV); foreach ($file as $row) { if(!is_null($row[0])) { \App\PostalCode::create([ 'first_code' => intval(substr($row[2], 0, 3)), 'last_code' => intval(substr($row[2], 3, 4)), 'prefecture' => $row[6], 'city' => $row[7], 'address' => (str_contains($row[8], '(')) ? current(explode('(', $row[8])) : $row[8] ]); } } } }
では、php artisan import:postal-code
を実行して郵便データを移動させましょう。(全12万件と、データ量がとても大きいので、少し時間がかかるはずです。PHPがタイムアウトする場合はphp.ini
の設定を変更して再度実行してみてください)
ちなみに、address
の部分では括弧書きの情報が入ってくる場合はがあるので、その場合は分割して最初の文字列を保存データとして採用しています。
【追記:2019.12.10】
CSVの内容によっては、最後の行でエラーが発生することがわかったのでif
文を追加しました。
検索データを扱いやすくするためにモデルに機能追加する
スコープで検索専用メソッドを追加する
postal_codes
テーブルはほぼ郵便番号からのみの検索になるはずですので、検索用のwhereメソッドをつくっておきましょう。
// Scope public function scopeWhereSearch($query, $first_code, $last_code) { $query->where('first_code', intval($first_code)) ->where('last_code', $last_code); }
このメソッドをPostalCode
モデルへ追加すると、以下のような使い方ができるようになります。
\App\PostalCode::whereSearch('131', '0045')->first();
また、first_code
とlast_code
はインデックスを使うために数値フィールドとしていますが、このままでは郵便番号としては間違っているので、accessor
を作って数値をゼロで埋めるように加工します。
// Accessor public function getFirstCodeAttribute($value) { return str_pad($value, 3, '0', STR_PAD_LEFT); } public function getLastCodeAttribute($value) { return str_pad($value, 4, '0', STR_PAD_LEFT); }
ではデータの中身を見てみましょう。
Array ( [id] => 39314 [first_code] => 131 [last_code] => 0045 [prefecture] => 東京都 [city] => 墨田区 [address] => 押上 )
ちなみにデータは12万件を超えていますが、インデックスのおかげでスピーディーにデータ取得できました。
Ajaxを通して住所データを取得する
では、ここからは実際にテキストボックスを作って郵便番号を入力し、Ajax経由で住所データを取得してみます。
ここでは、コントローラーでpostal_code.blade.php
がビューとして返るようにします。
public function postal_code() { return view('postal_code'); }
JavaScript部分をつくる
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script> <script> new Vue({ el: '#app', data: { firstCode: '', lastCode: '', prefecture: '', city: '', address: '' }, methods: { onClick: function() { const url = '/ajax/postal_search?'+ [ 'first_code='+ this.firstCode, 'last_code='+ this.lastCode ].join('&'); axios.get(url).then((response) => { this.prefecture = response.data.prefecture; this.city = response.data.city; this.address = response.data.address; }); } } }); </script>
Vue
とaxios
をcdnで読み込んで(←今回のコンセプトとしてはcdnはご法度ですが、テストで分かりやすいので使いました。あしからず。m_ _m)、各種変数を宣言します。
そして、ボタンをクリックしたときに起動するonClick
も追加。Ajax経由で郵便番号検索し、データが返ってきたら各種テキストボックスに反映するようにしています。
HTML部分をつくる
<div id="app"> <input type="text" v-model="firstCode"> - <input type="text" v-model="lastCode"> <button type="button" @click="onClick">郵便番号検索</button> <hr> 都道府県: <input type="text" v-model="prefecture"> 市区町村名: <input type="text" v-model="city"> それ以降: <input type="text" v-model="address"> </div>
テキストボックスにはVue側で宣言した変数をバインディングしています。また、@click
にonClick
も忘れず指定します。
すると、見た目はこうなります。
PHP側(郵便番号検索)を作る
最後に、Ajax通信でアクセスする先のコードを作成します。適当なコントローラーに以下のようなメソッドを追加してください。
public function search(Request $request) { return \App\PostalCode::whereSearch($request->first_code, $request->last_code)->first(); }
先ほど作ったwhereSearch()
をここで使います。
実際に検索してみる
では、実際に検索がどのようになるかチェックしてみましょう。
ボタンをクリックすると・・・・・・
うまくデータを反映することができました!
お疲れ様でした。
※ちなみに、今回はテストということで、送信データのバリデーションは全く行っていませんのでお気をつけください。
コードをダウンロードする
以下のボタンから、今回使ったテストコードをダウンロードできます。中身はLaravelのファイル構造のままで、今回使ったファイルのみが格納されていますので、適宜該当するフォルダへコピーして利用してください。
ただし、郵便番号データのCSVは入っていませんのでご自身で日本郵便のウェブサイトからダウンロードしてください。
※なお、autoloadがうまくいかない場合があると思いますが、その場合はcomposer dumpautoload -o
を実行してみてください。
ダウンロード:
郵便番号検索を実装するソースコードおわりに
ということで、今回は前から作っておきたかった郵便番号の検索をまとめてみました。
実は、記事を書く前はそれほどコード量が多くなるとは思っていなかったのですが、なんだかんだでいろいろとファイルが必要になってしまい少し複雑になってしまったかもしれません。
なので、今後もし時間ができたらlaravel-jp-postal-code
みたいな名前で独自パッケージを公開し、誰でも簡単に郵便番号検索ができるようにできればと考えています。完成したらこのページでお知らせするので、ぜひご期待くださいね。
ではでは〜。
【追記:2019.12.10】実際にパッケージを公開しました。詳しくは時短!郵便番号から住所が検索できるパッケージを公開しましたをご覧ください!