LoginSignup
56
51

More than 5 years have passed since last update.

ぐるナビAPIのサンプルコードでポエム書いた

Last updated at Posted at 2016-06-13

この記事を読む前に

この記事に悪意はありません.単に「公式のサンプルコードとしてこれは如何なものか…」と感じたので,初心者が困惑しないように適当に手直ししてみたまでです.

もちろん自分も汚いコードを存分に書いてきましたし,そもそもコードの美しさは個人の主観に左右されるところはあります.…が,それでもちょっとこれは…,という感じだったので,お許し下さい.

コード比較

ぐるナビのサンプル

問題のコードはこちらに掲載されています.こちらはターミナルへの出力を想定しています.

<?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でこれは酷くない?まだ

$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で返してくれるってステキ!

$obj  = json_decode($json);

お,第2引数にtrue渡してないから連想配列じゃなくてオブジェクトとして使うんだな.これは僕の好み.

foreach((array)$obj as $key => $val){

結局配列に戻すんかいな!どっちやねんw
ちなみにオブジェクトのままでもforeachにかけれます

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使わないと未定義エラー殺せないよ.

if (isset($restArray->id) && is_string($restArray->id)) ...

(そういえば,エラーが不安というわりに,file_get_contentsは堂々と使ってるのが気になってしょうがないな…)


以上です.偉そうなこと書いて申し訳ありませんでした.ただのポエムです.

56
51
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
56
51