Facebook APIとか

こんばんは、south37です。
今日は、MashupAward9(MA9)で使ったfacebook apiについての話をしたいと思います。画像系のアプリだったので画像の取得なんかに使いまくってました。どういう処理をして、どういう部分で苦しんだかについて書いていきたいと思います。

facebook sdk for php

開発ではphpを使ったので、facebook側が用意してくれたphpsdkを主に使いました。facebookの公式ドキュメントがかなり充実していて、それを読みながらであればとりあえず何でも出来た気がします。

実装としてはcontrollerから直接apiを叩いたりはせず、apiをラップするクラスを作ってそのメソッドを呼び出す事で整形したデータを返すようにしてました。名前付けとインターフェースが一番重要で、中身の処理はcontrollerからは抽象化されている感じですね。ほとんどがgraph apiをラップするだけの処理だったので、割とサクサク出来上がりました。特に、graph api explorerがむちゃくちゃ便利で、ここでapi呼び出しをいろいろ試して、ちゃんと返ってくるようならメソッドとしてまとめる、みたいな感じでした。

ちなみに、graph apiってのはfacebook apiの一種です。うまく説明出来ないんですが、userやalbumやimageみたいな情報の単位ごとにすべてidが割り振られていて、そのidさえ分かれば関連する情報を全て引っ張って来れるようになっています。例えば、userのidが分かればそのuserのfriendや持ってるalbumのidが分かって、さらにそこからimageのidを取得して....みたいに、一つのノードからどんどん周りのノードへと注意を広げていく事が出来て、まさにグラフからデータを引っ張って来れるようになっています。直感的で分かり易いのはいいとこかもですね。

面倒だったところ

で、ここからはいろいろ面倒だった事について書いていきたいと思います。まず一つ目は、facebook側でアプリを登録する際にアプリのドメインを指定しなければならないところ。ここで指定されたドメインからのアクセス(もしくはfacebook埋め込みのアプリからのアクセス)でなければ、facebook api(正しくはfacebookのアプリ認証機能)は使えなくなっています。ただ、やっぱりローカルで挙動を確認しながら使いたいなってことで、このへんのサイトを参考にして/etc/hostsを書き換えたりして対応してました。もともとVM上で開発してホストOSからブラウザで挙動確認する感じだったので、一瞬でした。こんなんで対応出来るんで、facebook側ではrefererをチェックしてるだけなのかなーとか思います。

次に、Facebook Api Exceptionについて。まず、これが謎のタイミングで大量に出てきました。しかも再現性が無い。おそらくfacebook側がバグってたんですが、割と対処のしようがなくて困りました。アプリの特性apiを叩きまくってたのですが、それが良く無かったのかなーとか思います。次に、api呼び出しはtry catchで囲んでるのに全然エラーが補足されませんでした。これは、僕らが使ってたSlimというWebフレームワークの謎仕様のせいなんじゃないかなと思ってます。Slimのドキュメントも見たんですがイマイチ対処法が分からなくて、結果Facebook Api Exceptionが出るたびに画面にでかでかとSlim errorが出力される事となりました。この辺は本当にストレスフルでした。

今ドキュメント見てて、もしかしたら$app->errorメソッドの中でerror補足の処理を書いておけば良かったのかもしれないと思い始めました。ただ、それって結局facebook apiからエラーが返ってきた時点で処理を強制的に止めちゃう事になるので、なんだかなーと思います。facebook apiのラッパークラスでは、apiのレスポンスがエラーなら空文字列を返すとか処理を分岐させるとかそういう事がしたかったので、どのみち望んでた処理は出来なかったのかもしれません。

エラー補足が出来ない関連で行くと、

<?php
  if($user_id) {

    // We have a user ID, so probably a logged in user.
    // If not, we'll get an exception, which we handle below.
    try {

      $user_profile = $facebook->api('/me','GET');
      echo "Name: " . $user_profile['name'];

    } catch(FacebookApiException $e) {
      // If the user is logged out, you can have a 
      // user ID even though the access token is invalid.
      // In this case, we'll get an exception, so we'll
      // just ask the user to login again here.
      $login_url = $facebook->getLoginUrl(); 
      echo 'Please <a href="' . $login_url . '">login.</a>';
      error_log($e->getType());
      error_log($e->getMessage());
    }   
  } else {

    // No user, print a link for the user to login
    $login_url = $facebook->getLoginUrl();
    echo 'Please <a href="' . $login_url . '">login.</a>';

  }

みたいな公式ドキュメントに載ってるコードが使えませんでした。実は、facebook sdkのgetUserIdでuserIdが取得出来るかどうかがユーザーがアプリにログインしているかどうかの判断基準となるのですが、何らかの事情でuserIdは取得出来るのにアクセストークンは間違っているという事がけっこうあります。上記の例では、その判断の為にtry catchで囲んだapi呼び出しが行われています。アクセストークンが変わるケースはいくつかあって、例えばtemporaryなアクセストークンはログアウトのたびに変わるみたいですし、長期のアクセストークンもfacebook側のパスワードが変更されたり6ヶ月たつと使えなくなってしまいます。その判断が今回のアプリでは出来なかったので、アプリ内にログイン機構を作って、パスワードの変更は行われない前提での設計をしました。かなり苦し紛れでした。このへんはもうちょっとSlimと格闘する必要がある気がします。

また、facebook apiのレスポンスが予想以上に遅い事にも苦しみました。なんだかんだで一度取得した情報は全部DBにつっこんであったので、条件分岐をいれてDB内にデータがあればDBから取得するようにすれば良かったかもしれません。ただし、そうするとfacebook側での情報の変更(ユーザーのアルバムの追加やアルバム内の写真の追加)を検知出来なくなるので、多少のレスポンスの遅さはしょうがないのかもしれません。

全体的に歯切れが悪い感じになっちゃいましたが、とりあえず今回はこんな感じです。