自前の郵便番号辞書(SQLite)を構築して、問い合わせフォームに、郵便番号から、住所を挿入します。 よくAPIを使用する方法を紹介しているサイトを見かけますが、お金をもらっているプロが、 他人のふんどしとは、クライアントに失礼ですね。APIだと、提供元が突然サービスを中止すれば、 すべてのクライアントにご迷惑をおかけすることになります。 サービスを停止しなくても、サービスの重い日は、クライアントの動作も重くなり、トラブルの原因になりかねません。 ここでは、SQLiteを使用して、データベースを構築します。 SQLiteなら、本格的なデータベースサーバも必要なく、単独で動作しますので、使いまわしも便利です。
こんなイメージですね。郵便番号欄に7桁の郵便番号を指定して[住所検索]ボタンを押してみてください。
いかがですか、自サイト内に設置されたデータベースから取得していますので、他のサイトや、通信状況に影響されることなく、高速に動作します。郵便番号辞書の構築
- それでは、localhostをVSCodeで開き、ディレクトリ[zip]を作成しておきます。 (デフォルトではC:\xampp\htdocs、または、C:\www\example.com) 一式をここにおいていれば、簡単にコピーして使いまわせます。
- 次に、ブラウザを起動し、日本JPの郵便番号のダウンロードページを開きます。
少しスクロールすると全国一括が有りますので、これをダウンロードします。 ダウンロードしたファイルは圧縮されていますので、解凍して、先ほどのディレクトリzipに置きます。 - 解凍したファイルをzipに置くと、VSCodeでも表示されています。 このCSVファイルを、SQLiteのデータベース形式に変換する必要が有りますので、 変換用のスクリプトを作成します。同じくzip内にcreatedb.phpを作成してください。
- それでは、コードを書いてゆきます。
<?php ini_set('display_errors', "On"); $filepath = "./KEN_ALL.CSV"; $dbname = "zip.db"; $dbh = new PDO("sqlite:./$dbname"); $tables =getTABLEname($dbh); if (!in_array('zip', $tables)) { $sql = "CREATE TABLE IF NOT EXISTS zip( zip VARCHAR(10) NOT NULL PRIMARY KEY, pre VARCHAR(10) NOT NULL, city VARCHAR(50) NOT NULL, area VARCHAR(100) NOT NULL );"; $stmt = $dbh->query($sql); if (($handle = fopen($filepath, "r")) !== FALSE) { /* 必ずトランザクションを使用すること */ $dbh->query("BEGIN TRANSACTION;"); $i = 1; while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) { mb_convert_variables("UTF-8", "ASCII,SJIS,SJIS-win", $data); $sql = "INSERT INTO zip VALUES ('". $data[2]. "','". $data[6]. "','". $data[7]. "','". $data[8]. "');"; $dbh->query($sql); echo sprintf("%d:[%07d] %s%s%s\n", $i, $data[2], $data[6], $data[7], $data[8]); $i++; } /* トランザクションはコミットして初めて保存される */ $dbh->query("COMMIT;"); fclose($handle); } } $dbh = null; exit; function getTABLEname($dbh) { /* すべてのテーブル名を配列で返す */ $sql = "SELECT name FROM sqlite_master WHERE type='table'"; $stmt = $dbh->query($sql); $Tables = array(); while ($row = $stmt->fetch(PDO::FETCH_NUM)) { ${'Tables'}[] = $row[0]; } return $Tables; } ?>
2行目は、バグ取り中は、エラーを画面に表示してください。というおまじないで、 4行目で、ダウンロードした、郵便番号CSVのパスを指定。 5行目は、作成するSQLiteのファイル名を指定しています。 6行目で、PHPデータオブジェクト(PDO)のオブジェクトを作成して、変数$dbhに格納しています。 この時点で、指定したファイル名が存在しなければ、SQLiteのデータベースファイルが作成されます。 存在すれば、読み書き可能な準備をしてオープンされます。
8行目は、開いたデータべースに存在する、すべてのテーブルを取得して、配列$tablesに格納しています。 この、getTABLEname関数は、最下で記述している汎用関数ですので、最終的にはクラスにします。 9行目で、取得したテーブルの中にzipが存在すれば、何もしないで終了し、存在しない場合にだけ以下に進みます。 10行目から15行目で、テーブルzipを作成するSQLを定義し、16行目で作成しています。
18行目で、CSVファイルを開き、20行目でトランザクションを開始しています。 SQLiteは、ロック機能が弱いため、書き込み時は、たとえ1行でもトランザクションを使用してください。 今回は、数万行ですので、トランザクションを使用しない選択肢はありません。 使用しなければ、速度も100倍以上低下します。 SQLiteの動作が、異常に重いと思われていた方は、ここに原因が有るのかも知れません。
23行目は、windowsの文字コード、Shift-JISで書かれたCSVを、UTF-8に変換しています。 24行目は、SQLのインサート文を定義し、26行目で書き込んでいます。 27行目は、必須ではありませんが、標準出力に書き込んだ内容を表示しています。 31行目で、トランザクションをコミットし、32行目で、CSVを閉じて、書き込みの終了です。
最後に、データベースを閉じています。 以上で、郵便番号辞書生成プログラムの完成です。 - それでは、このプログラムを実行してみましょう。 VSCodeの右上にある実行ボタンを押します。 変換済みの行を表示するように指定していますので、デバッグコンソールに次々と表示されます。 今回のダウンロードデータは、11万8千ぎょうあまりでした。 終了すると、ディレクトリ、zip内にzip.dbが追加されています。
郵便番号辞書の利用
完成した郵便番号の辞書を、今度は、実際にフォームで使用できるようにしましょう。- まず最初に、検索エンジンを作りますので、ディレクトリzip内に、getcity.phpを作成します。
検索機能は、郵便番号用のクラスを作成し、クラス内のクラスメソッドを呼び出すことにします。 オブジェクトにしないで、クラスメソッドを呼び出すのであれば、単純な関数にしても良いと思いますが、 クラスにするのは、郵便番号に関する機能をまとめておけば、メンテナンスが簡単になることと、 関数だと、同じ関数名が使用できませんので、大きなシステムでは、汎用性が悪くなります。 チームで開発している場合は、なおさらです。 クラスメソッドにすれば、クラスが違えば、同じ関数名が使用できます。<?php ini_set('display_errors', "On"); header("Content-type:text/html;charset=utf-8"); echo GetZip::get_city($_GET['zip']); exit; class GetZip { static function get_city($zip, $db = "./zip.db") { $zip = mb_convert_kana($zip, "n"); $zip = preg_replace("/-/", "", $zip); if (preg_match("/^\d{7}$/", $zip)) { $dbh = new PDO("sqlite:$db"); $sql = "SELECT * FROM zip WHERE zip='$zip' LIMIT 1;"; $stmt = $dbh->query($sql); if ($buff = $stmt->fetch(PDO::FETCH_OBJ)) { $city = join(",", array( preg_replace("/(\d{3})(\d{4})/", "$1-$2", $obj->zip), $obj->pre, $obj->city, $obj->area )); } $dbh = null; } return ($city); } } ?>
クラスメソッドget_cityの内部を見てみましょう。
11行目では、受け取った郵便番号が、全角の可能性もありますので、強制的に半角に変換しています。 12行目は、ハイフンを削除して、7桁の数値のみにしています。 13行目で、受け取った郵便番号が、7桁の数値の場合のみ検索するように、正規表現で調べています。 数値以外の文字が含まれている場合や、6桁以下または、8桁以上の数値でも検索は行われません。 15行目で、データベースオブジェクトを作成し、16行目でSQL文を定義して、17行で実行しています。 18行目で、結果を取得し、結果が存在すれば、19行目で、各要素をカンマ区切りの文字列にして返しています。
get_cityメソッドは完成です。
4行目は、webで使用することが前程ですので、出力がUTF-8で有ることを宣言後、get_cityの結果を出力しています。
この時点で、ブラウザから、
localhost/zip/getcity.php?zip=1010035
と入力して、住所が表示されれば、検索エンジンも完成です。 - 次に、問い合わせ用の、HTMlドキュメントを作成しますので、zip内にinquiry.htmlを作成します。
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>お問い合わせフォーム</title> <link href="/common/jquery-ui-1.13.1.custom/jquery-ui.css" type="text/css" /> <script src="/common/jquery-ui-1.13.1.custom/external/jquery/jquery.js"></script> <script src="/common/jquery-ui-1.13.1.custom/jquery-ui.js"></script> <script type="text/javascript"> $(document).ready(function() { $('input[type=button],input[type=submit], input[type=file], input[type=reset],button').button(); }); </script> </head> <body> </body>
今回は、フォームをリロードしないで、Ajaxで住所を挿入しますので、 JavaScriptを簡素化するため、汎用ライブラリ、jQueryと、jQuery uiを使用します。
汎用のライブラリを保存する場所として、ディレクトリ/commonを作成しておきます。
jQuery uiのダウンロードページを開き、 一番下までスクロールして、[Download]ボタンを押してください。 ダウンロードしたファイルは、圧縮されていますので、解凍して、先ほどの、commonに置いてください。 common内に、jQueryのファイルが展開されていれば大丈夫です。
htmlの6行目から、8行目は、commonのjQuery関連を呼び出しています。
つぎに、<body></body>内にフォームを作成します。<form action="" method="post" name="mailform" id="mailform"> <table cellspacing="0" cellpadding="10" id="formtable"> <tr><th colspan="2" class="ui-widget-header">お問い合わせフォーム</th></tr> <tr><td class="fieldtitle">氏名</td> <td><input type="text" name="name" /></td></tr> <tr><td class="fieldtitle">住所</td> <td><p><input type="text" name="zip" id="Zip" placeholder="7桁数値" style="width:80px;" /> <input type="button" value="住所検索" onclick=" getAjaxcity('mailform,Zip,Pre,City,Addr', './');" /></p> <p style="margin-top:2px;"><input type="text" name="pre" id="Pre" placeholder="都道府県" style="width:80px;" /> <input type="text" name="city" id="City" placeholder="市区町村" style="width:80px;" /> <input type="text" name="addr" id="Addr" placeholder="町域以下" style="width:160px;" /></p> <p style="margin-top:2px;"><input type="text" name="buill" id="buill" placeholder="ビル、マンション名" style="max-width:356px;" /></p></td></tr> <tr><td class="fieldtitle">Email</td> <td><input type="text" name="email" /></td></tr> <tr><td class="fieldtitle">お問い合わせ</td> <td><textarea style="max-width:360px;height:160px;"></textarea></td></tr> <tr><th colspan="2" class="ui-widget-header"><input type="button" value="送信する" style="width:120px;margin:20px;"></th></tr> </table> </form>
モバイルと共有する、レスポンシブデザインの場合は、テーブルタグを使用することは好ましくありませんが、 今回は、テストですので、簡単にデザインできる、テーブルタグを使用しています。 重要なのは、郵便番号と住所に関するフィールドには、一意のIDが、付けられていることです。 JavaScriptは、このIDを元に、フィールドを識別して、データを挿入します。 9ぎょう目には、Ajaxで住所を取得するコマンドを呼び出すボタンが設置されています。 この、getAjaxcityは、パラメーターに、この問い合わせフォームの、フォーム名と、 郵便番号フィールドのID、都道府県のID、市区町村のID、町域以下のIDと、郵便番号検索エンジンのパスを引数に渡しています。 今回は、同じディレクトリ内に、getcity.phpが有りますので、./になっています。 見た目を良くするために、styleも追加しておきましょう。<style type="text/css"> #formtable {width:600px;margin:50px auto;} #formtable td {border-bottom:#888 dashed 1px;} .fieldtitle {text-align:center;} input[type=text],input[type=password],select,textarea { padding:6px; border: solid #aaa 1px; border-radius:4px; width:100%; max-width:240px; } #ziplist {width:100%;border:solid #aaa 1px;} #ziplist th {background:#ddd;} #ziplist tr.line:hover {background:#8f8;cursor:pointer;} </style> </head>
- それでは、最も重要な、エージャックスで、ゲットシティー.phpを呼び出して、それぞれのフィールドに挿入するJavaScriptです。
<script type="text/javascript"> $(document).ready(function() { $('input[type=button],input[type=submit], input[type=file], input[type=reset],button').button(); }); function getAjaxcity(param, path) { if (!path) path = "/zip/"; var Param = param.split(","); if (!param) Param = array('mailform','Zip','Pre','City','Addr'); var url = ''; var zip = $('#'+Param[1]).val(); if (!zip) { alert('7桁の郵便番号を指定してください。\nPlease specify the 7-digit postal code.'); $('#'+Param[1]).focus(); return; } url = path + 'getcity.php?zip='+zip; $.get(url, function(data) { var Address = data.split(","); if (Address[0] == '') { alert('指定の郵便番号で見つかりません。'); $('#'+Param[1]).focus(); } else { $('#'+Param[1]).val(Address[0]); if (!Param[3]) { $('#'+Param[2]).val(Address[1] + Address[2] + Address[3]); } else if (!Param[4]) { $('#'+Param[2]).val(Address[1]); $('#'+Param[3]).val(Address[2] + Address[3]); } else { $('#'+Param[2]).val(Address[1]); $('#'+Param[3]).val(Address[2]); $('#'+Param[4]).val(Address[3]); } } }); } </script>
関数、getAjaxcityは、パラメータにフォームの各IDと、データベースのパスを受け取ります。 パスが指定されていない場合は、デフォルトに、/zip/を設定し、パラメータが未記入の場合も、デフォルトを設定しています。 11行目は、郵便番号フィールドの値を取得し、郵便番号が入力されていなければ、エラーダイアログを表示して終了します。 17行目で、データベースのパスを作成し、18行目で、jQueryの$.getで、Ajax通信しています。 getcity.phpとの通信で、戻り値が、変数$dataに帰ってきますので、19行目で、戻り値を、都道府県、市区町村、町域以下に分割しています。 このため、getcity.phpは、データをカンマ区切りで出力していたのです。 20行目は、0番目のデータがカラの場合は、見つからない旨のエラーメッセージを表示して終了します。 25行目以降は、住所のフィールドが1つの場合、都道府県と、それ以降の2つの場合、 都道府県、市区町村、町域以下の3つに分割されている場合で、挿入方法を変更しています。
- まず最初に、検索エンジンを作りますので、ディレクトリzip内に、getcity.phpを作成します。
最後に、inquiry.htmlのソースです。
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>お問い合わせフォーム</title> <link href="/common/jquery-ui-1.13.1.custom/jquery-ui.css" type="text/css" /> <script src="/common/jquery-ui-1.13.1.custom/external/jquery/jquery.js"></script> <script src="/common/jquery-ui-1.13.1.custom/jquery-ui.js"></script> <script type="text/javascript"> $(document).ready(function() { $('input[type=button],input[type=submit], input[type=file], input[type=reset],button').button(); }); function getAjaxcity(param, path) { if (!path) path = "/zip/"; var Param = param.split(","); if (!param) Param = array('mailform','Zip','Pre','City','Addr'); var url = ''; var zip = $('#'+Param[1]).val(); if (!zip) { alert('7桁の郵便番号を指定してください。\nPlease specify the 7-digit postal code.'); $('#'+Param[1]).focus(); return; } url = path + 'getcity.php?zip='+zip; $.get(url, function(data) { var Address = data.split(","); if (Address[0] == '') { alert('指定の郵便番号で見つかりません。'); $('#'+Param[1]).focus(); } else { $('#'+Param[1]).val(Address[0]); if (!Param[3]) { $('#'+Param[2]).val(Address[1] + Address[2] + Address[3]); } else if (!Param[4]) { $('#'+Param[2]).val(Address[1]); $('#'+Param[3]).val(Address[2] + Address[3]); } else { $('#'+Param[2]).val(Address[1]); $('#'+Param[3]).val(Address[2]); $('#'+Param[4]).val(Address[3]); } } }); } </script> <style type="text/css"> #formtable {width:600px;margin:50px auto;} #formtable td {border-bottom:#888 dashed 1px;} .fieldtitle {text-align:center;} input[type=text],input[type=password],select,textarea { padding:6px; border: solid #aaa 1px; border-radius:4px; width:100%; max-width:240px; } #ziplist {width:100%;border:solid #aaa 1px;} #ziplist th {background:#ddd;} #ziplist tr.line:hover {background:#8f8;cursor:pointer;} </style> </head> <body> <form action="" method="post" name="mailform" id="mailform"> <table cellspacing="0" cellpadding="10" id="formtable"> <tr><th colspan="2" class="ui-widget-header">お問い合わせフォーム</th></tr> <tr><td class="fieldtitle">氏名</td> <td><input type="text" name="name" /></td></tr> <tr><td class="fieldtitle">住所</td> <td><p><input type="text" name="zip" id="Zip" placeholder="7桁数値" style="width:80px;" /> <input type="button" value="住所検索" onclick=" getAjaxcity('mailform,Zip,Pre,City,Addr', './');" /></p> <p style="margin-top:2px;"><input type="text" name="pre" id="Pre" placeholder="都道府県" style="width:80px;" /> <input type="text" name="city" id="City" placeholder="市区町村" style="width:80px;" /> <input type="text" name="addr" id="Addr" placeholder="町域以下" style="width:160px;" /></p> <p style="margin-top:2px;"><input type="text" name="buill" id="buill" placeholder="ビル、マンション名" style="max-width:356px;" /></p></td></tr> <tr><td class="fieldtitle">Email</td> <td><input type="text" name="email" /></td></tr> <tr><td class="fieldtitle">お問い合わせ</td> <td><textarea style="max-width:360px;height:160px;"></textarea></td></tr> <tr><th colspan="2" class="ui-widget-header"><input type="button" value="送信する" style="width:120px;margin:20px;"></th></tr> </table> </form> </body> </html>