【Laravel】郵便番号で住所検索する(ダウンロード可)

さてさて、Laravel5.7の新機能をLaravel 5.7 の新機能まとめで一通り触ってみたのでこの辺で以前から作っておきたかった、ある機能をつくることに時間を割くことにしました。

その機能とは、郵便番号検索です。

つまり、131-0045と郵便番号を入れて検索すると東京都墨田区押上というデータを取得できる機能ですね。

もちろん、JavaScriptにほんの少し設定を追加するだけで、この機能を実装できるajaxzip3という素晴らしいサービスがあることは知っています。(というかよく利用させていただいてます。ありがとうございます。m_ _m)

が、クライアントさんによっては「将来的になくなるかもしれないサービスは極力使わない」方針の方がいらっしゃるのも事実です。実際、ajaxzipは過去にGoogle CodeからGitHubへ移行していますし、Googleは特によくサービスを終了してしまうので、下手するとcdnなどもなくなってしまう可能性だってあるわけです。

そこで、今回は独自の郵便番号検索を作ってみることにします。ぜひみなさんも参考にしてみてくださいね。

やりたいこと

冒頭部分でも書きましたが、以下のような流れを作ります。

  1. 郵便番号を<input type=”text”>に入力する
  2. ボタンをクリックするとAjax経由で郵便番号に対応する住所を検索する
  3. 住所データを取得し、住所入力ボックスで表示する

※ 開発環境は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');
    });
}

内容は、

  1. ID ・・・ オートインクリメントで連番
  2. first_code ・・・ 郵便番号の始め(3桁)の部分
  3. last_code ・・・ 郵便番号の後ろ(4桁)の部分
  4. prefecture ・・・ 都道府県名
  5. city ・・・ 市区町村
  6. address ・・・ それ移行の住所

となっています。

ちなみに、郵便データはマスターデータなので$table->timestamps();は削除しています。(つまり、created_atupdated_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) {

            \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の部分では括弧書きの情報が入ってくる場合はがあるので、その場合は分割して最初の文字列を保存データとして採用しています。

検索データを扱いやすくするためにモデルに機能追加する

スコープで検索専用メソッドを追加する

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_codelast_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>

Vueaxiosをcdnで読み込んで(←今回のコンセプトとしてはcdnはご法度ですが、テストで分かりやすいので使いました。あしからず。m_ _m)、各種変数を宣言します。

そして、ボタンをクリックしたときに起動するonClickも追加。Ajax経由で郵便番号検索し、データが返ってきたら各種テキストボックスに反映するようにしています。

HTML部分をつくる

<div id="app">
    <input type="text" v-model="firstCode">&nbsp;-&nbsp;<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側で宣言した変数をバインディングしています。また、@clickonClickも忘れず指定します。

すると、見た目はこうなります。

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みたいな名前で独自パッケージを公開し、誰でも簡単に郵便番号検索ができるようにできればと考えています。完成したらこのページでお知らせするので、ぜひご期待くださいね。

ではでは〜。