この記事を読む前に
この記事に悪意はありません.単に「公式のサンプルコードとしてこれは如何なものか…」と感じたので,初心者が困惑しないように適当に手直ししてみたまでです.
もちろん自分も汚いコードを存分に書いてきましたし,そもそもコードの美しさは個人の主観に左右されるところはあります.…が,それでもちょっとこれは…,という感じだったので,お許し下さい.
コード比較
ぐるナビのサンプル
問題のコードはこちらに掲載されています.こちらはターミナルへの出力を想定しています.
<?php
/*****************************************************************************************
ぐるなびWebサービスのレストラン検索APIで緯度経度検索を実行しパースするプログラム
注意:緯度、経度、範囲の値は固定で入れています。
アクセスキーはユーザ登録時に発行されたキーを指定してください。
*****************************************************************************************/
//エンドポイントのURIとフォーマットパラメータを変数に入れる
$uri = "http://api.gnavi.co.jp/RestSearchAPI/20150630/";
//APIアクセスキーを変数に入れる
$acckey= "input your accesskey";
//返却値のフォーマットを変数に入れる
$format= "json";
//緯度・経度、範囲を変数に入れる
//緯度経度は日本測地系で日比谷シャンテのもの。範囲はrange=1で300m以内を指定している。
$lat = 35.670083;
$lon = 139.763267;
$range = 1;
//URL組み立て
$url = sprintf("%s%s%s%s%s%s%s%s%s%s%s", $uri, "?format=", $format, "&keyid=", $acckey, "&latitude=", $lat,"&longitude=",$lon,"&range=",$range);
//API実行
$json = file_get_contents($url);
//取得した結果をオブジェクト化
$obj = json_decode($json);
//結果をパース
//トータルヒット件数、店舗番号、店舗名、最寄の路線、最寄の駅、最寄駅から店までの時間、店舗の小業態を出力
foreach((array)$obj as $key => $val){
if(strcmp($key, "total_hit_count" ) == 0 ){
echo "total:".$val."\n";
}
if(strcmp($key, "rest") == 0){
foreach((array)$val as $restArray){
if(checkString($restArray->{'id'})) echo $restArray->{'id'}."\t";
if(checkString($restArray->{'name'})) echo $restArray->{'name'}."\t";
if(checkString($restArray->{'access'}->{'line'})) echo (string)$restArray->{'access'}->{'line'}."\t";
if(checkString($restArray->{'access'}->{'station'})) echo (string)$restArray->{'access'}->{'station'}."\t";
if(checkString($restArray->{'access'}->{'walk'})) echo (string)$restArray->{'access'}->{'walk'}."分\t";
foreach((array)$restArray->{'code'}->{'category_name_s'} as $v){
if(checkString($v)) echo $v."\t";
}
echo "\n";
}
}
}
//文字列であるかをチェック
function checkString($input)
{
if(isset($input) && is_string($input)) {
return true;
}else{
return false;
}
}
?>
私が書いたサンプル
以下の質問の要望をベースに書いています.こちらはWebブラウザへの出力を想定しています.
<?php
/**
* HTMLにテキストを出力する際は必ずこの関数を通す
*/
function h($str)
{
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}
// エンドポイントとパラメータを定義
$endpoint = 'http://api.gnavi.co.jp/RestSearchAPI/20150630/';
$params = [
'keyid' => 'XXXXXX',
'format' => 'json',
'latitude' => '35.670083',
'longitude' => '139.763267',
'range' => '1',
];
// リクエスト実行
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $endpoint . '?' . http_build_query($params, '', '&'),
CURLOPT_RETURNTRANSFER => true, // レスポンスボディを返り値として取得
CURLOPT_FAILONERROR => true, // HTTPステータス400以上はエラーと見なす
CURLOPT_ENCODING => 'gzip', // 通信を圧縮する
]);
$response = curl_exec($ch);
// エラーのときはテキストとしてエラーメッセージを出して終了
if ($response === false) {
header('Content-Type: text/plain; charset=UTF-8', true, 500);
exit(curl_error($ch));
}
// API側がクソ実装してなければこのjson_decodeは必ず成功するはず
$obj = json_decode($response);
// HTMLとして表示
header('Content-Type: text/html; charset=UTF-8');
?>
<!DOCTYPE html>
<title>Example</title>
<?php foreach ($obj->rest as $r): ?>
<table border="1">
<caption><?=h($r->name)?></caption>
<tr>
<th>ID</th>
<td><?=h($r->id)?></td>
</tr>
<tr>
<th>店舗名</th>
<td><?=h($r->name)?></td>
</tr>
<tr>
<th>アクセス</th>
<td><?=h($r->access->line)?>・<?=h($r->access->station)?>から<?=h($r->access->walk)?>分</td>
</tr>
</table>
<?php endforeach; ?>
何がいけないのか
(ここでお腹いっぱいな人は先は読まなくていいです)
(ほんとに読まなくていいよ)
(…言っちゃうよ?いいの?…わかった)
//URL組み立て
$url = sprintf("%s%s%s%s%s%s%s%s%s%s%s", $uri, "?format=", $format, "&keyid=", $acckey, "&latitude=", $lat,"&longitude=",$lon,"&range=",$range);
C言語では`sprintf`ゴリ押しはよく使うけど,さすがにPHPでこれは酷くない?まだ
```php
$url = sprintf('%s?format=%s&keyid=%s&latitude=%s&longitutde=%s&range=%s', $uri, $format, $acckey, $lat, $lon, $range);
のほうがマシだよね?というかhttp_build_query
使えばいいよね?
$url = $url . '?' . http_build_query([
'format' => $format,
'keyid' => $acckey,
'latitude' => $lat,
'logitude' => $lon,
'range' => $range,
], '', '&');
で,どうせなら最初から配列部分そのまま定義しようぜ…ってことで
$endpoint = 'http://api.gnavi.co.jp/RestSearchAPI/20150630/';
$params = [
'keyid' => 'XXXXXX',
'format' => 'json',
'latitude' => '35.670083',
'longitude' => '139.763267',
'range' => '1',
];
$endpoint . '?' . http_build_query($params, '', '&'); // をCURLOPT_URLに指定
みたいな感じにしました.
($uri
から$url
が生成されるのも変数名としてナンセンスだったのでそこも気をつけました)
$json = file_get_contents($url);
まあこれはいいんだけど,`@`演算子つけないと運用しにくい関数なんてできる限り使いたくないので,個人的にはcURL推しです.エラーでもWarning吐かずに静かに`curl_error`で返してくれるってステキ!
> ```php
$obj = json_decode($json);
お,第2引数にtrue渡してないから連想配列じゃなくてオブジェクトとして使うんだな.これは僕の好み.
foreach((array)$obj as $key => $val){
結局配列に戻すんかいな!どっちやねんw
ちなみにオブジェクトのままでもforeachにかけれます
>```php
foreach((array)$obj as $key => $val){
if(strcmp($key, "total_hit_count" ) == 0 ){
echo "total:".$val."\n";
}
if(strcmp($key, "rest") == 0){
foreach((array)$val as $restArray){
...
}
}
}
またC言語っぽさ出てきてますね.PHPでは文字列比較はstrcmp
なんか使わなくても===
演算子でできるんやで.あと==
使ってるので(普通はならないけど)もしfalse
になったときにfalse == 0
でtrueになっちゃうのもなんかモヤモヤしちゃう…そもそも!strcmp()
みたいに省略するならまだアリとして,==
で書くぐらいなら===
使おうや.
てかさ,このforeachいらんやろ!!!
echo "total: $obj->total_hit_count\n";
foreach ($val->rest as $rest) {
....
}
これでええやん…
存在確認いりますか?僕の例ではステータスコード400以上弾いてるので大丈夫っす.
それでもAPIの動作が信用できない?自分のサービスは信用しましょうよ!
foreach((array)$val as $restArray){
if(checkString($restArray->{'id'})) echo $restArray->{'id'}."\t";
if(checkString($restArray->{'name'})) echo $restArray->{'name'}."\t";
if(checkString($restArray->{'access'}->{'line'})) echo (string)$restArray->{'access'}->{'line'}."\t";
if(checkString($restArray->{'access'}->{'station'})) echo (string)$restArray->{'access'}->{'station'}."\t";
if(checkString($restArray->{'access'}->{'walk'})) echo (string)$restArray->{'access'}->{'walk'}."分\t";
foreach((array)$restArray->{'code'}->{'category_name_s'} as $v){
if(checkString($v)) echo $v."\t";
}
echo "\n";
}
PHPにおけるオブジェクトのプロパティは`$restArray->{'id'}`じゃなくても`$restArray->id`でアクセスできるよ!これが`json_decode`でオブジェクトとしてデコードした最大のメリットや!
あとさ…`checkString`連呼してるけど,そんなに不安なんかな?さっきから明示的な`(array)`キャストとか冗長なforeachとか書いてるから不安な気持ちはよくわかるけど,これ意味ないで?エラー怖いならこうっちゃう?`isset`使わないと未定義エラー殺せないよ.
```php
if (isset($restArray->id) && is_string($restArray->id)) ...
(そういえば,エラーが不安というわりに,file_get_contents
は堂々と使ってるのが気になってしょうがないな…)
以上です.偉そうなこと書いて申し訳ありませんでした.ただのポエムです.