【Laravel】郵便番号から住所を自動入力する機能の作り方

Laravelで郵便番号から住所を自動入力する機能を作る。LaravelでAPIを作ってJavaScriptからAPIを叩くことで実現する。

郵便番号と住所のデータを入手する

まずは,以下からデータを入手する。

郵便番号データのダウンロード

全国一括データ (加工済バージョン)をダウンロードして,x-ken-all.csvを開く。

必要なのは,C,G,H,Iの4列だけなので,その他の列は削除する。

[名前を付けて保存]を選び,ファイル形式をCSV UTF-8で保存する。

データの置き場所はdatabase/seedersにする。

マイグレーションとモデルの作成

マイグレーションファイルを作成。

$ sail artisan make:migration create_addresses_table

マイグレーションファイルを編集。

public function up()
    {
        Schema::create('addresses', function (Blueprint $table) {
            $table->id();
            $table->integer('zip');
            $table->string('pref');
            $table->string('city');
            $table->string('town');
            $table->timestamps();
        });
    }

マイグレーションを実行。

$ sail artisan migrate

モデルを作成。

$ sail artisan make:model Address

seederの作成と実行

seederを作成する。

sail artisan make:seeder AddressesSeeder
<?php

namespace Database\Seeders;

use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Support\Facades\DB;        <--これを入れ忘れないように
use Illuminate\Database\Seeder;

class AddressesSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $this->command->info("addressesの作成を開始します...");

        $memberSplFileObject = new \SplFileObject(__DIR__ . '/x-ken-all.csv');
        $memberSplFileObject->setFlags(
            \SplFileObject::READ_CSV |
            \SplFileObject::READ_AHEAD |
            \SplFileObject::SKIP_EMPTY |
            \SplFileObject::DROP_NEW_LINE
        );

        foreach ($memberSplFileObject as $key => $row) {
            //excelでcsvを保存するとBOM付きになるので削除する
            if ($key === 0) {
                $row[0] = preg_replace('/^\xEF\xBB\xBF/', '', $row[0]);
            }

            DB::table('addresses')->insert([
                'zip' => (int) trim($row[0]),
                'pref' => trim($row[1]),
                'city' => trim($row[2]),
                'town' => trim($row[3]),
            ]);
        }
        $this->command->info("addressesを作成しました。");
    }
}

seederを実行する。

$ sail artisan db:seed --class=AddressesSeeder

レコードが12万件ほどあるので処理が終了するまでしばらく待つ。以下のメッセージが出れば成功。

addressesの作成を開始します...
addressesを作成しました。
Database seeding completed successfully.

ルーティング

ルーティングを追加。web.phpではなくapi.phpに書き込む。ここでは省略するが顧客情報の入力などで利用する場合はユーザー認証しないとアクセスできないようにミドルウェア->middleware(['auth'])を挿入することになる。

use App\Http\Controllers\Api\AddressController;

Route::get('/address/{zip}', [AddressController::class, 'address']);

コントローラーの設置

コンロトーラーを設置する。APIでCRUD機能を設置する場合はオプション --apiを付けると便利だが,今回は目的が異なるので不要。

$ sail artisan make:controller Api/AddressController
<?php

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Address;               <--これを入れ忘れないように
use Illuminate\Http\Request;

class AddressController extends Controller
{
    public function address($zip)
    {
        $address = Address::where('zip', intval($zip))->first();
        return response()->json(
            $address,
            200,
            [],
            JSON_UNESCAPED_UNICODE
        );
    }
}

これでAPIの設置は完了。

APIのテスト

ここで,APIがちゃんと動いているかどうかを確認する。

アドレスバーに http://localhost/api/address/640803 と入力して

{"id":52,"zip":640803,"pref":"北海道","city":"札幌市中央区","town":"南三条西","created_at":null,"updated_at":null}

が表示されればAPIが機能している。

ビューの設置

郵便番号から住所を自動入力する機能はユーザーの新規登録などで利用する。ここでは話を省略するためにユーザーのテーブルは作成せず,郵便番号を入力したら住所をフィールドに挿入するだけのコードを紹介する。

まずは,ビューの作成。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <p>郵便番号:<input id="zip" type="text" name="zip" size="7">例:1020072(半角数字)</p>
    <button class="api-address" type="button">住所を自動入力</button>
    <p>住所:<input id="address" type="text" name="address" size="30"></p>
</body>
<script>
    //イベントリスナの設置:ボタンをクリックしたら反応する
    document.querySelector('.api-address').addEventListener('click', () => {
        //郵便番号を入力するテキストフィールドから値を取得
        const elem = document.querySelector('#zip');
        const zip = elem.value;
        //fetchでAPIからJSON文字列を取得する
        fetch('../api/address/' + zip)
            .then((data) => data.json())
            .then((obj) => {
                //郵便番号が存在しない場合,空のオブジェクトが返ってくる
                //オブジェクトが空かどうかを判定
                if (!Object.keys(obj).length) {
                    //オブジェクトが空の場合
                    txt = '住所が存在しません。'
                } else {
                    //オブジェクトが存在する場合
                    //住所は分割されたデータとして返ってくるので連結する
                    txt = obj.pref + obj.city + obj.town;
                }
                //住所を入力するテキストフィールドに文字列を書き込む
                document.querySelector('#address').value = txt;
            });
    });
</script>
</html>

話を省略するためにJavaScriptのコードを直接書き込んでいるが,本来はファイルを分けた方が良い。

これで完成。