Hudsonとphp

php用のHudsonの設定と言えば、

HudsonでPHPユニットテスト
http://d.hatena.ne.jp/ssogabe/20081102/1225642743

の記事があったりしますが、こちらで紹介されてる設定をそのままコピペしたとしても
近頃(すくなくとも今年4月以降)のhudsonだとパスがうまくいかなかったりします(うろおぼえ)

なので、Phingプラグインを使う場合は、ビルドパスとか以下の感じにすると動くと思います。

というか、最近だったらSebastian Bergmann純正のテンプレートにそうのがトレンディーじゃないでしょうかね
http://github.com/sebastianbergmann/php-hudson-template


あ、PEAR的にはCruise Control&PHPUnderControlがナウでヤングですよ
http://wiki.php.net/pear/qa/ci

symfonyドキュメント翻訳温泉ツアーに参加してきました。

http://www.symfony.gr.jp/blog/20100801-symfony-translation-spa

symfonyドキュメントの翻訳は一切やってません(キリッ! (おいおい)


yudoufuさんの寝起きマル秘画像を激写はできなかったので、別の写真をお楽しみください。
P1040057
P1040027
P1040074
P1040075

やったことはちょっとgithubにプッシュしたり、温泉行ったり、vimperatorにeijiroプラグインまで入れてたのに電子辞書使ってて電池切れしてたり、
温泉に行ったり、オペラの発声練習を聞いたり、温泉に行ったりってことですね。参加者の皆さんお疲れ様でした。

強欲にHTTPレスポンスをUTF-8にエンコーディングするDiggin_Http_Response_Charsetについて

githubもしくはopenpearでのコミットログを見ていると気づいてるかと思われますが、
最近は、Diggin_Http_Response_Charsetというのを作成しました。
http://openpear.org/package/Diggin_Http_Response_Charset
http://github.com/sasezaki/Diggin_Http_Response_Charset


利用方法のわかりやすい例として、Zend_Http_Responseのラッパーを用意しました。

もともと、Htmlscrapingのエンコーディング部分だけを切り離して、Diggin_Http_Response_Encodingというクラスに置き換えてたのですが、
Http_Response_Encodingという名前は、「"content-encoding"などを無視していると弾言できる(Enjoy!)」*1と思い、ちょびっと手直ししたもの*2を作成しているところでした。
ですが、CharactorEncodingというのがタイポなのと、以下のisssueトラッカーにお寄せいただいたバグのために書き換えが必要だったという次第です。
http://code.google.com/p/diggin/issues/detail?id=1


バグの内容は、レスポンスのconten-typeが'Shift-JIS'だったために、
'SJIS-win'な○付き1とかが化けるという事象です。


細かくどう変更したかは、
Htmlscrapingクラス、Diggin_Http_Response_Encoding、
Diggin_Http_Response_Charset_Detector_Htmlをそれぞれ見比べてもらうとして、

・ mb_list_encodings()の中で、mb_preferred_mime_name()を通すと変更されるものは、
デフォルトでレスポンスボディ(HTML)をmb_detect_encoding()強制するようにした。
(SJIS-winなど)
・ デフォルトのdetectエンコーディングリストを
'ASCII, JIS, UTF-8, eucJP-win, EUC-JP, SJIS-win, SJIS'にした(かなり適当です)

という所です。


デフォルトのエンコード方法としては、上述のDetectorの結果をDiggin_Http_Response_Charset_Encoder_Htmlが用いてエンコードします。
使いかたの例としては、以下のような形で、レスポンスボディ(HTML)とレスポンスヘッダーのcontent-typeを渡します(conten-typeがない場合はnull)。
http://github.com/sasezaki/Diggin_Http_Response_Charset/blob/master/sandbox.php
デフォルトではDiggin_Http_Response_Charset_Encoder_Htmlである程度動作すると思われますが、特定のサイトにurlの正規表現などで、エンコード方法を細かく指定したい場合もあります。

そのために、Diggin_Http_Response_Charset_Front_UrlRegexというのを用意しました。
これは、addEncoderメソッドに対象のURL正規表現とDiggin_Http_Response_Charset_Encoder_EncoderAbstractを拡張したエンコーダーを設定し細かいエンコード設定ができます。使い方の例としては、
http://github.com/unpush/p2-php/blob/master/cookie.php
のように、ブラウザでも化けるようなHTMLの特定の一部についても、
http://github.com/sasezaki/Diggin_Http_Response_Charset/blob/master/sandbox_github.php
のような感じで、_initBody()という事前フィルタ処理を行うことで、HTMLすべてUTF-8変換というところまで試みてます。


なお、まだリリースしてないDiggin 0.7ではこのDiggin_Http_Response_Charsetを使います。すでに変更対応分のコミットはしてます。
http://openpear.org/repository/Diggin_Scraper_Adapter_Htmlscraping/trunk/library/Diggin/Scraper/Adapter/Htmlscraping.php
0.7では投入しようとした、robots.txtなどのハンドラー"Diggin_RobotRules"を一旦ギブアップしてもう近々リリースするつもりです。

たった1行のコードでHTML取得&解析をしたい場合はexthtmlが便利

たった*行のコードでHTML取得&解析をしたい場合はWeb::Scraperが便利exthtmlを使った場合、こんな感じになります。

$ exthtml -x '//div[@id="topicsfb"]//li//text()' -a='Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)' http://www.yahoo.co.jp/

詳しくは、exthtmlの解説ページで。


cpan分からないという方には、phpのインスパイア版(exthtml.php)があります。

extphp -x '#topicsfb li' -v text http://www.yahoo.co.jp -a 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)'

詳しくは、digginリファレンスで(あ、exthtml.phpのこと書いてないや)

某テンプレートエンジンの各HTTPクライアントで100リクエスト投げた時のベンチマーク

大体3,4回目の結果を載っけてます。

環境:

$ \php -v
PHP 5.3.2-1ubuntu4 with Suhosin-Patch (cli) (built: Apr  9 2010 08:23:39) 
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2010 Zend Technologies
$ pear list | grep Net_Socket
Net_Socket              1.0.9    stable
$ pear list -a | grep HTTP
HTTP_Client             1.2.1    stable
HTTP_Request            1.4.4    stable
HTTP_Request2           0.5.2    alpha
$ pear list -a | grep ZF
INSTALLED PACKAGES, CHANNEL PEAR.ZFCAMPUS.ORG:
ZF      1.10.5  stable
$ pecl list | grep http
pecl_http  1.6.6   stable


file_get_contents

$ cat file_get.php 
<?php
for ($i=1; $i <=100; $i++) {
    file_get_contents('http://localhost/favicon.ico');
}
$ time \php file_get.php 

real    0m0.107s
user    0m0.044s
sys     0m0.036s


pear/HTTP_Request

$ cat pear_httpRequest.php 
<?php
require_once 'HTTP/Request.php';
for ($i=1; $i <=100; $i++) {
    $req = new HTTP_Request('http://localhost/favicon.ico');
    $req->sendRequest();
}
$ time \php pear_httpRequest.php 

real    0m0.212s
user    0m0.148s
sys     0m0.044s


pear/HTTP_Request2

$ cat pear_httpRequest2.php 
<?php
require_once 'HTTP/Request2.php';
for ($i=1; $i <=100; $i++) {
    $req2 = new HTTP_Request2('http://localhost/favicon.ico');
    $req2->send();
}
$ time \php pear_httpRequest2.php 

real    0m0.190s
user    0m0.160s
sys     0m0.012s


ZFのZend_Http_Client

$ cat zf_http.php 
<?php
require_once 'Zend/Http/Client.php';
for ($i=1; $i <=100; $i++) {
    $client = new Zend_Http_Client('http://localhost/favicon.ico');
    $client->request();
}
$ time \php zf_http.php 

real    0m0.267s
user    0m0.172s
sys     0m0.036s


CURL(sudo apt-get php5-curlしたやつ)

$ cat cURL.php 
<?php
for ($i=1; $i <=100; $i++) {
    $fh = fopen('/tmp/null', 'w');
    $ch = curl_init('http://localhost/favicon.ico');
    curl_setopt($ch, CURLOPT_FILE, $fh);
    curl_exec($ch);
    curl_close($ch);
    fclose($fh);
}
$ time \php cURL.php 

real    0m0.114s
user    0m0.032s
sys     0m0.052s


pecl_http(pecl installでウィザードは全部Enter)

$ cat pecl_http.php 
<?php
for ($i=1; $i <=100; $i++) {
    http_get('http://localhost/favicon.ico');
}
$ time \php pecl_http.php 

real    0m0.091s
user    0m0.040s
sys     0m0.032s


pecl_http(その2)

$ cat pecl_http_class.php 
<?php
for ($i=1; $i <=100; $i++) {
    $r = new HttpRequest('http://localhost/favicon.ico', HttpRequest::METH_GET);
    $r->send();
}
$ time \php pecl_http_class.php 

real    0m0.093s
user    0m0.036s
sys     0m0.040s


pecl_http(pool版 1回めと2回め以降で全然違う。。。)

$ cat pecl_http_pool.php 
<?php
$pool = new HttpRequestPool;
for ($i=1; $i <=100; $i++) {
    $pool->attach(new HttpRequest('http://localhost/favicon.ico'));
}

$pool->send();
$ time \php pecl_http_pool.php 

real    0m6.796s
user    0m0.664s
sys     0m1.580s
$ time \php pecl_http_pool.php 

real    0m0.124s
user    0m0.056s
sys     0m0.044s


lithium0.9.5

$ cat lithium-095.php 
<?php
require_once 'SplClassLoader.php';
$loader = new SplClassLoader();
$loader->setIncludePath(__DIR__ . '/libraries');
$loader->register();

for ($i=1;$i<=100;$i++) {
    $http = new lithium\net\http\Service(array('host' => 'localhost'));
    $r = $http->get('/favicon.ico');
}

$ time \php lithium-095.php 

real    0m0.178s
user    0m0.108s
sys     0m0.040s

ただのsocket_clientだとかsfWebBrowserとかHordeだとかは誰かやってください。


参照:
LLごとの標準的なHTTPクライアントで100リクエスト投げた時のベンチマーク
http://subtech.g.hatena.ne.jp/mala/20100531/1275322139


ついき:ちなみに「そんなリクエストごとにクライアントオブジェクト生成しないわー」と思って、下のもやってみましたが、あんまりかわりませんでした。あれ。

<?php
require_once 'Zend/Http/Client.php';
$client = new Zend_Http_Client();
for ($i=1; $i <=100; $i++) {
    $client->setUri('http://localhost/favicon.ico');
    $client->request();
}

PHPとZend Frameworkでの前の例外



私の見てるマニュアルには載ってますよ!
http://framework.zend.com/manual/ja/zend.exception.previous.html


Zend Frameworkを使ってる方は、PHP5.2でもprevious exceptionを可能にするために以下のとおり変更が施されて、codeは(int)キャストしてることに注意。
http://framework.zend.com/code/browse/Standard_Library/standard/trunk/library/Zend/Exception.php

ちなみに、

<?php
require_once 'Zend/Exception.php';

function throwing() {
    throw new InvalidArgumentException();
}

function get() {
    throwing();
}   
    
try {   
    try {
        get();
    } catch (Exception $e){
        throw new Zend_Exception("", 10, $e);
    }
} catch (Exception $e) {
    echo $e;
    echo PHP_EOL.'----'.PHP_EOL;
    var_dump($e->getTraceAsString());
    var_dump($e->getPrevious()->getTraceAsString());
}

を実行すると、

exception 'InvalidArgumentException' in /tmp/previous_test.php:5
Stack trace:
#0 /tmp/previous_test.php(9): throwing()
#1 /tmp/previous_test.php(14): get()
#2 {main}

Next exception 'Zend_Exception' in /tmp/previous_test.php:16
Stack trace:
#0 {main}
----
string(9) "#0 {main}"
string(87) "#0 /tmp/previous_test.php(9): throwing()
#1 /tmp/previous_test.php(14): get()
#2 {main}"

となる。で、これが実運用上ロギングとのかねあいでうんにゃこりゃがあると思いますが、僕Javaってなんじゃばなので一家言ありません。。(Zend Serverー?)


PHPマニュアルのException:
http://www.php.net/manual/ja/exception.construct.php