A Day In The Boy's Life -98ページ目

A Day In The Boy's Life

とあるエンジニアのとある1日のつぶやき。

今まで「Twitterへ投稿するPHPスクリプト 」や「Twitterのタイムラインを取得するPHPスクリプト 」を書いていて、データを取得するURLだけ変えればいけんじゃね、って思ってたら意外とはまった。



Twitterのフォロワーを取得するPHPスクリプト


ここまでは、今までどおりの書き方を少し工夫するだけでいけます。


<?php

require_once "HTTP/Client.php";

$username = "yourname";
$password = "yourpassword";
// 取得したいユーザーのscreen_nameを指定
$page = "statuses/followers/it_boy.xml";

$basic = array('Authorization'=>'Basic '.base64_encode($username.':'.$password));

$client = new HTTP_Client(null, $basic);
$client->get("http://twitter.com/".$page );
$response = $client->currentResponse();
$body = mb_convert_encoding($response['body'], "EUC-JP","auto");

$xml = new SimpleXMLElement($body);

for ($i = 0; $i < count($xml->user); $i++) {
    echo $xml->user[$i]->screen_name . "\n";
}

上記だと最大100人のフォロワーを直近のツイート付きで取得することが出来ます。

で、問題は次の100件を取得したい場合。


Twitter API 仕様書 (勝手に日本語訳シリーズ) @ 観測気球


のAPIの日本語訳を見ると、followersのAPIのオプションとしてpageと言うものがあるので、これで次の100件が取得できるかと思いきや同じユーザー一覧が返ってきます。

で、本家の方のAPIのドキュメントを見ると


Twitter REST API Method: statuses followers @ Twitter API Wiki


pageオプションがない・・・。(仕様が変わった?)

変わりに、cursorオプションを使えと書いています。

と言うことで、プログラムを改変。ちなみにcursorオプションは「-1」からスタートする必要があるらしい。


<?php

require_once "HTTP/Client.php";

$username = "yourname";
$password = "yourpassword";
// 取得したいユーザーのscreen_nameを指定
$page = "statuses/followers/it_boy.xml?cursor=-1";

$basic = array('Authorization'=>'Basic '.base64_encode($username.':'.$password));

$client = new HTTP_Client(null, $basic);
$client->get("http://twitter.com/".$page );
$response = $client->currentResponse();
$body = mb_convert_encoding($response['body'], "EUC-JP","auto");

$xml = new SimpleXMLElement($body);

for ($i = 0; $i < count($xml->users->user); $i++) {
    echo $xml->users->user[$i]->screen_name . "\n";
}


※ 返り値のXMLのフォーマットも異なっているので注意


まず、一番最初はcursorのオプションを「-1」でアクセスし、100件分のフォロワーを取得します。

すると、返ってきたXMLの一番最後に


<next_cursor>1324680976289962274</next_cursor>
<previous_cursor>0</previous_cursor>


という値がついてきます。

つまり、このカーソルの値を操作して次の100件を取得しに行くような仕様のようです。

(個人的にはpageオプションでよいのではないかと思いますが・・・)


なので、次の100件を取得したい場合のプログラムは


$page = "statuses/followers/it_boy.xml?cursor=1324680976289962274";


のように書く必要が出てきます。(上記だと私のフォロワーを取得しますので、取得したい人のscreen_nameを適宜指定します)

cursorはフォロワー数が変動すると変化していくようです。

前の100件を取得したい場合は、「previous_cursor」の値を指定すると取得できます。


ちなみに、自分のフレンド(自分がフォローしているユーザー一覧)を取得したい場合は、「statuses/followers.xml」じゃなくて「statuses/friends.xml」へアクセスすると取得できます。(XML形式で取得したい場合)

次の100人を取得したい場合も同様です。





配列を分解して別の変数に取り込みたいときに、その配列のキーから一つずつ取り出していくコードを書くと全体としてソースの量が増えたり、見づらくなったりします。

そういったときに、list関数 を使えば短いコードで簡単に実現できたりします。



文字列を分解してlistで一気に取り込む


例えば、date関数で日付を取得し、それをそのままの形で使いたいんだけど、一部のロジックでその中の年や月だけを利用したいと言った場合、explodeで文字列分解しつつその内容をダイレクトに変数に取り込むことができます。


- explodeした内容を各々の変数に一気に格納する
<?php

$date = date('Y/m/d');
list($year, $month, $day) = explode("/", $date);

echo $date . "\n";
echo $year . "-" . $month;


これは、下記のように何回もdate関数を呼び出す必要がありません。


<?php

$year  = date('Y');
$month = date('m');
$day   = date('d');

echo $year . "/" . $month . "/" . $day . "\n";
echo $year . "-" . $month;


この使い方は、上記以外でもCSVからデータを取り込む際に、各行ごとのデータをわかりやすい変数に格納していくことができたりします。


<?php

$fp = fopen("./hoge.csv", "r");

while(list($num, $name, $email) = fgetcsv($fp, 1024)) {
    echo "number:" . $num . "\n";
    echo "name:" . $name . "\n";
    echo "email:" . $email . "\n";
}


配列を返す関数からlistで一気に別の変数に取り込む


元々listは配列から別変数に取り込むために用意されていたりしますが、ユーザー関数を作りそれが配列を返す仕様にしていると、受け取り先でまた分解してと言うことが面倒になる場合もあります。

そういうときに、listを使って一気に別変数に値を格納させることが出来ます。


<?php

function getFruits() {
    return array("apple", "banana", "orange");
}

list($apple, $banana, $orange) = getFruits();


なお、listは受け取る側の変数(list内で定義した変数)が少ない分にはエラーが出ません。

なので、関数の仕様が変わり、戻り値の配列の中の数が変化しても影響を受けません。

(list内の受け取り側の変数の数の方が多くなってしまうと構文エラーがでます。)


<?php

function getFruits() {
    return array("apple", "banana", "orange");
}

list($apple, $banana) = getFruits();


listでは基本的に配列を返す仕様のものであれば、何でも取り込むことが出来るのですが、連想配列は分解して受け取ることができません。

また、配列のキーは0から順番にそろってなければいけません。

下記のように、キーが飛び飛びの場合は構文エラーが発生します。


<?php

$array[0] = "apple";
$array[3] = "banana";
$array[5] = "orange";

list($val1, $val2, $val3) = $array;

PHPの関数では、配列を返すものも多いため、組み合わせ次第では便利に使える関数ではないでしょうか。

例えば、DBの検索クエリの結果をダイレクトに別変数に格納したり。


<?php

$conn = pg_pconnect("dbname=hoge");

$result = pg_query($conn, "select name, email from test_tab");

while (list($name, $email) = pg_fetch_row($result)) {
    echo $name . "/" . $email . "\n";
}




前回の「GO2WEB20のTwitterバッチをAmebloにつけてみた。と、IE対策方法 」に端を発してjQueryを使って外部のJavaScriptのソースを読み込む際の様々なやり方の検証をしてみる。



append()を使ってscriptタグを追加する


jQueryのappend()を使ってタグを追加できますのでこれでscriptタグを追加してみます。


<script>
$(document).ready(function(){

    var script_tag     = document.createElement("script");
    script_tag.type    = "text/javascript";
    script_tag.src     = "hoge.js";
    script_tag.charset = "utf-8";

    $("body").append(script_tag);

})
</script>


function foo() {
    alert('hello');
}

foo();


これは、上手く動作します。

ただ、FireBugなんかでHTMLを見てみると、scriptタグが追加(表示)されていません。


- FireBugで見てみても追加したscriptタグは表示されない
A Day In The Boy&#39;s Life-jQuery-append1


これは、jQueryがhtmlに挿入後に即時にscriptタグを削除する仕様のようで、append()以外でもprepend()やbefore()、after()でも同様のようです。


参考: jQuery の挙動を解読する(32):jQuery.clean() メソッド解読──jQuery解読(49) @ anything from here


しかし実際には、append実行時には即時に読み込んだJavaScriptが実行されてます。

ってことは、先にappendしたscript内で定義した関数なんかは呼び出し可能なんでしょうか。

タグ自体がHTML要素内で削除されているので、なんだか呼び出しできない気がします。

ということで、下記のようにJavaScriptを分けて各パターンで実行してみます。


<script type="text/javascript" src="foo.js"></script>
<script>
  foo();
</script>

同じことをするために2つのscriptタグを生成してbodyにappendしてみます。


<script>
$(document).ready(function(){

    var script_tag1     = document.createElement("script");
    script_tag1.type    = "text/javascript";
    script_tag1.src     = "foo.js";
    script_tag1.charset = "utf-8";

    $("body").append(script_tag1);

    var script_tag2     = document.createElement("script");
    script_tag2.type    = "text/javascript";
    script_tag2.charset = "utf-8";
    script_tag2.text    = "foo();";

    $("body").append(script_tag2);

})
</script>


これも動いた!

HTML要素からタグが削除されるていても、先行して読み込んだJavaScript内の関数を呼び出すことは可能なようです。

FireBugのHTML要素では見えないんですが、DOMとしては関数fooが存在していることがわかります。


A Day In The Boy&#39;s Life-jQuery-append2



直接scriptタグを書いてappendToしてみる


書き方としては、さっきの逆になる。


<script>
$(document).ready(function(){

    $("<script type='text/javascript' charset='utf-8' src='foo.js'></scri" + "pt>").appendTo("body");
    $("<script type='text/javascript' charset='utf-8'>foo();</scri" + "pt>").appendTo("body");

})
</script>

これも問題なく動作します。
scriptタグを分割せずにそのまま書いてしまうと「unterminated string litera」と言うことで構文エラーになってしまいます

ですので、scriptの閉じタグを適当なところで分割し、文字列として挿入することで回避するようにします。


参考: Firefox下でのunterminated string literalエラーを回避する @ OKの日記


また、scriptタグがFireBug上で表示されないのはappndの時と同様です。

しかし、先行してappendToしたスクリプト内の関数は、後からapendToしたスクリプト内から実行することは可能です。


こちらの書き方は、単純なJavaScriptなら良いですが、ソースが長くなるとかなり見づらくなってしまいますので適用範囲が限られてくるかもしれません。



$.getScriptを利用して外部JavaScriptをロードする


$.getScriptではHTTPリクエストを通じて外部のJavaScriptファイルを呼び出すことができます。

また、対象のJavaScriptファイルの読込みが成功した場合に実行する関数(コールバック関数)を定義できるため、シンプルで見やすいソースを書くことができます。


- $.getScriptを利用して外部JSファイルをロードし、ロード成功後に関数を実行する

<script>
$(document).ready(function(){
    $.getScript("foo.js", function(){
      foo();
    });
})
</script>


他の書き方より随分とシンプルですね。

foo.jsファイルのロードが正常に完了すると、コールバック関数として第2引数のfunction()内が実行されます。

ですので、ロードするJavaScript内で定義された関数を実行したかったり、その中で定義している変数を書き換えたければコールバック関数内で設定することで、別のJavaScriptをロードする必要がありません。


しかも、$.getScriptを使った場合、JavaScriptの実行を他のスクリプトより遅延して実行させることができます

今までの書き方を複合させて、下記のように書いてみます。

スクリプトの実行順序がわかりやすいように、引数にスクリプト名を渡し、それをalertで表示させるようにしています。

ちなみにfoo1.jsからfoo3.jsまでは中身が全く同じJavaScriptファイルです。


<script>
$(document).ready(function(){

    $.getScript("foo1.js", function(){
      foo('script1');
    });

    $("<script type='text/javascript' src='foo2.js'></scri" + "pt>").appendTo("body");
    $("<script type='text/javascript' charset='utf-8'>foo('script2');</scri" + "pt>").appendTo("body");

    var script_tag1     = document.createElement("script");
    script_tag1.type    = "text/javascript";
    script_tag1.src     = "foo3.js";
    script_tag1.charset = "utf-8";

    $(script_tag1).appendTo("body");

    var script_tag2     = document.createElement("script");
    script_tag2.type    = "text/javascript";
    script_tag2.charset = "utf-8";
    script_tag2.text    = "foo('script3');";

    $("body").append(script_tag2);
})
</script>

これを実行してみると「script2 → script3 → script1」の順でalertが表示されます

スクリプトのロード自体が遅れるのかと思いましたが、そういうわけではなくFireBugで見てみるとロード自体は上から順にされています。


- FireBugで状況をみると$.getScriptでロードしたJSファイルが最初に読み込まれている
A Day In The Boy&#39;s Life-FireBug-コンソール


コールバック関数の実行自体が遅延されるのかとも考えましたが、そういうわけではなく各JSファイルで直接alertを出すようにして読み込んでみても$.getScriptでロードしたスクリプトが一番最後に実行されました。


JavaScriptの動的ロードというのは一般的ですが、その中でも実行順序に変化をもたせたいという場合に、使えるかもしれません。