[Symfony2]入出力の文字エンコードを変換してみよう Symfony Advent Calender JP 2011 – 6日目-

Written by uechoco 12月 06
この記事を読む時間:358くらい

@uechocoです。Symfony Advent Calendar JP 2011の6日目の記事です。@fivestrさんからのバトンタッチです。

日本Symfonyユーザー会に所属するスタッフとして、勢いで申し込んでみたものの、
普段職場でSymfonyを使ってないのでネタ探しに苦労しました。

そういえば去年のSymfony Advent 2010 : ATNDでは[Symfony2]PEAR::Net_UserAgent_MobileをDIコンテナから呼び出すという記事を書きました。
今年も似たような領域で、ガラケー向けのWebサイト開発で使えるかもしれないテクニックとして、入出力される値やコンテンツの文字コードを変換する方法をご紹介します。

symfony 1.x系ではMojavi由来のフィルタチェインの機構がありましたので、
MobileEncodingFilterなどのクラスを作って、入出力の前後でエンコードの変換をかませるというのが一般的だったかと思います。

Syfmony2系ではフィルタチェインという概念はなくなってしまいました。
代わりに処理の所々にイベントが定義されていて、リスナーを登録しておくと自動的にディスパッチしてくれるようなイベントドリブン方式に変わりました。

今回はSymfony2のHttpKernel機構に標準で設定されているkernel.requestイベントとkernel.responseイベントのリスナークラスとしてMobileEncodingListenerを作り、入出力のエンコード処理を行なっています。今回はSymfonyに標準で同梱されているAcme\DemoBundleの中に作成する前提です。

MobileEncodingListenerクラスの作成

まずはイベントリスナークラスを作成します。Symfony本体のFrameworkBundleやHttpKernelコンポーネントのディレクトリ構造の慣習に従ってAcme\DemoBundleの下にEventListenerディレクトリを作成し、その下にイベントリスナークラスを作成します。以下のようなMobileEncodingListnerクラスを作成してください。まだ中身は実装しません。

[phpcode]

*
*/
class MobileEncodingListener
{
}
[/phpcode]

kernel.responseイベント

まずは簡単なkernel.responseイベントに対するメソッドを記述します。クラスの使用宣言を追加して、MobileEncodingListenerクラスにonKernelResponse()メソッドを実装します。

[phpcode]
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
[/phpcode]

[phpcode]
/**
* Convert output encoding of response.
* @param FilterResponseEvent $event
*/
public function onKernelResponse(FilterResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}

$response = $event->getResponse();

$response->setContent(mb_convert_encoding($response->getContent(), ‘SJIS-win’, ‘UTF-8’));
}
[/phpcode]

中身の処理としては単純で、Resopnseオブジェクトを取得し、その中のコンテンツの文字コードを変換してまたセットしなおしているだけです。今回は説明の簡略化のために文字コードの部分を直書きしています。最初の行のマスターリクエスト以外なら何もしないという条件文がありますが、HttpKernelではリクエストの種別が2種類あり、マスターリクエストとサブリクエストと呼ばれています。そのマスターリクエストの時だけ処理したいので条件式を記述しています。FilterResponseEventクラスを受け取るというのはkernel.responseイベントとの仕様として決められています。

kernel.requestイベント

次にkernel.requestイベントに対するメソッドを記述します。先ほどと同様にクラスの使用宣言を追加して、MobileEncodingListenerクラスにonKernelRequest()メソッドを実装します。

[phpcode]use Symfony\Component\HttpKernel\Event\GetResponseEvent;[/phpcode]

[phpcode]
/**
* Convert input encoding of request.
* @param GetResponseEvent $event
*/
public function onKernelRequest(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}

$request = $event->getRequest();

// GET
$get = $request->query->all();
mb_convert_variables(‘UTF-8’, ‘SJIS-win’, $get);
$request->query->replace($get);
// POST
$post = $request->request->all();
mb_convert_variables(‘UTF-8’, ‘SJIS-win’, $post);
$request->request->replace($post);
}
[/phpcode]

こちらもマスターリクエストのみ処理する記述が最初にあります。次にRequestオブジェクトを取得し、今回は$_GETと$_POSTに相当する変数だけを文字コード変換しました。$_GETと$POSTはRequestオブジェクトの$queryと$requestというパブリック変数に代入されています。形式としてはParameterBagというクラスでラッピングされて代入されています。ParameterBagクラスのインターフェースとしてall()メソッドで全配列を取得し、replace()メソッドで全配列を総取替しています。

イベントリスナーとして登録する

最後に、先ほど作ったクラスとメソッドをイベントリスナーとして登録します。登録手順はDIコンテナにサービスとして登録するのと同じですが、特別なタグを定義することでイベントリスナーとして認識されるようになっています。src/Acme/DemoBundle/Resources/config/services.xmlを開き、以下のように追記してください。

  1. <!-- 省略 -->
  2.  
  3.     <parameters>
  4.         <parameter key="mobile_encoding_listener.class">Acme\DemoBundle\EventListener\MobileEncodingListener</parameter>
  5.     </parameters>
  6.  
  7.     <services>
  8.         <!-- 省略 -->
  9.         <service id="acme.demo.mobile_encoding_listener" class="%mobile_encoding_listener.class%">
  10.             <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" />
  11.             <tag name="kernel.event_listener" event="kernel.response" method="onKernelResponse" />
  12.         </service>
  13.     </services>
  14.  
  15.     <!-- 省略 -->

確認する

何事もなければデモ画面が正常に動作すると思います。プロファイラのEvent項を見てみると、リスナーが登録されていることが確認できます。

今回直書きだった文字コードの指定は、config.ymlなどの設定ファイルから取ってくるような仕様にしたり、UserAgentを判別して決定するような仕様にしたほうが汎用性が高いでしょう。

余談

今回の記事の参考にするために他のイベントリスナークラスを見ていましたが、イベントリスナークラスの実装は様々なパターンが存在しているようです。例えば1つのリスナークラスに2つ以上のイベントメソッドをもたせているものや、1クラス1イベントメソッドのもの、他の役割のあるクラスにイベントメソッドを持たせてリスナークラスを兼任させているものなどです。

SymfonyのHttpKernelにどのようなイベントがあるか知りたい方は、Symfonyの内部構造のドキュメントの一節にイベントについて書かれている部分がありますので、そちらを参照してください。

最後に

7日目はMongoDB JPにも所属している@madapajaさんです。よろしくお願いします。

[php]homebrewでintlライブラリが有効なphpをコンパイルする

Written by uechoco 12月 04
この記事を読む時間:165くらい

Symfony2などを使用する場合、php 5.3のintlという国際化用拡張モジュールがインストールされていないと警告が出たり一部の機能が使用することができません。

私のMacBook Airに標準で入っているphpは5.3.6ですが、intlライブラリは含まれていません。

そこで、今回はMac OSX 10.7 Lionのhomebrewでintlライブラリが有効なphpをコンパイルし、加えてSymfony2のcheck.phpと呼ばれる環境チェックスクリプトで警告が出ないようにしてみます。私のphpは標準のものに対してライブラリを加えているため、皆さんよりも追加でインストールする項目が少ないかもしれません。

intlオプションが有効なphp formula

phpは標準でインストールされているため、homebrewのformulaは有りません。そこで、非公式に作られているformulaを使用します。
ココらへんの手順はいくつかの記事を参考にしました。

[shcode]brew install https://github.com/adamv/homebrew-alt/raw/master/duplicates/php.rb –with-mysql –with-intl[/shcode]

このとき、使用可能なオプションはoptionsコマンドで見ることができます。

  1. brew options https://github.com/adamv/homebrew-alt/raw/master/duplicates/php.rb
  2. php
  3. --with-mysql
  4.     Include MySQL support
  5. --with-mariadb
  6.     Include MariaDB support
  7. --with-pgsql
  8.     Include PostgreSQL support
  9. --with-mssql
  10.     Include MSSQL-DB support
  11. --with-fpm
  12.     Enable building of the fpm SAPI executable (implies --without-apache)
  13. --without-apache
  14.     Build without shared Apache 2.0 Handler module
  15. --with-intl
  16.     Include internationalization support
  17. --without-readline
  18.     Build without readline support
  19. --with-gmp
  20.     Include GMP support

依存関係のあるlibxml2, mcrypt, icu4cなどもコンパイルされたあと、php本体もコンパイルされます。少し時間がかかるので、気長に待ちましょう.。

apacheの設定

phpのコンパイルが終了すると、以下のようなメッセージが出てきます。

  1. For 10.5 and Apache:
  2.     Apache needs to run in 32-bit mode. You can either force Apache to start
  3.     in 32-bit mode or you can thin the Apache executable.
  4.  
  5. To enable PHP in Apache add the following to httpd.conf and restart Apache:
  6.     LoadModule php5_module    /usr/local/Cellar/php/5.3.8/libexec/apache2/libphp5.so
  7.  
  8. The php.ini file can be found in:
  9.     /usr/local/etc/php.ini
  10.  
  11. 'Fix' the default PEAR permissions and config:
  12.     chmod -R ug+w /usr/local/Cellar/php/5.3.8/lib/php
  13.     pear config-set php_ini /usr/local/etc/php.ini

例えば、Webサーバーがapache であれば「LoadModule php5_module /usr/local/Cellar/php/5.3.8/libexec/apache2/libphp5.so」を/etc/apache2/httpd.confに追加しまししょう。(元々入っているのphp5_moduleの行があるので、そちらは先頭に「#」をつけてコメントアウトしておきましょう。)

また新しいphpのiniファイルは「/usr/local/etc/php.ini」にありますので標準の場所と異なるのを気をつけましょう。

PEARライブラリののパーミッションも直しておくと便利です。「chmod -R ug+w /usr/local/Cellar/php/5.3.8/lib/php」というコマンドはすぐに通るかも知れませんが、「pear config-set php_ini /usr/local/etc/php.ini」のときに「/User/hogehoge/.pearrc」が作れないといったエラーがあった場合は、touchコマンドなどで事前に作成してあげて、パーミッションを777にでもしておいてください。

この時点で「php –version」としても、まだ元々のphpコマンドの方を参照しているでしょう。「which php」とすると「/usr/bin/php」を参照しているためです。コマンドラインが読み込まれた段階でbrew側のコマンド群を優先的に読みこむようにしましょう。ここではシェルがbashの想定で設定ファイルを書きます。

[shcode]vi ~/.bash_profile[/shcode]

としてこの1行を追加しましょう。

  1. export PATH="$(brew --prefix)/bin:$PATH"

最後に、bashの設定を反映させます。

[shcode]source ~/.bash_profile[/shcode]

この時点でphp –versionすると、新しいほうのphpが参照されているでしょう。

  1. PHP 5.3.8 (cli) (built: Dec  4 2011 13:40:01)
  2. Copyright (c) 1997-2011 The PHP Group
  3. Zend Engine v2.3.0, Copyright (c) 1998-2011 Zend Technologies

timezoneの設定

新しいphp.iniが作成されたので、最低限の設定としてtimezoneの設定をしておかないと、いずれ警告が出てしまいます。

[shcode]vi /usr/local/etc/php.ini[/shcode]

diff形式でいうと、以下のように変更してください。

  1. -;date.timezone =
  2. +date.timezone = "Asia/Tokyo"

APCのインストール

この時点でintl込のphpが動くのですが、最後にSymfony2用にAPCライブラリもコンパイルして用意しましょう。

[shcode]brew install apc[/shcode]

と打つだけです。

インストール後、以下のようなメッセージが表示されます。

  1. To finish installing APC:
  2.  * Add the following lines to php.ini:
  3.     [apc]
  4.     extension="/usr/local/Cellar/apc/3.1.9/apc.so"
  5.     apc.enabled=1
  6.     apc.shm_segments=1
  7.     apc.shm_size=64M
  8.     apc.ttl=7200
  9.     apc.user_ttl=7200
  10.     apc.num_files_hint=1024
  11.     apc.mmap_file_mask=/tmp/apc.XXXXXX
  12.     apc.enable_cli=1
  13.  * Restart your webserver
  14.  * Copy "/usr/local/Cellar/apc/3.1.9/apc.php" to any site to see APC's usage.

書いてあるとおりに、「/usr/local/etc/php.ini」にapcの設定を追加し、webサーバーを再起動します。最後のスクリプトのコピーは、APCの便利スクリプトなので、今はいりません。

Symfony2のapp/check.phpを見る

Symfony2のapp/check.phpを使って、環境が整っているかチェックしてみましょう。(その前にSymfony2の本体をDLしておいてください。)

Symfony2のルートディレクトリに移動し、以下のコマンドでapp/check.phpを実行します。

[shcode]php app/check.php[/shcode]

  1. ********************************
  2. *                              *
  3. *  Symfony requirements check  *
  4. *                              *
  5. ********************************
  6.  
  7. php.ini used by PHP: /usr/local/etc/php.ini
  8.  
  9. ** WARNING **
  10. *  The PHP CLI can use a different php.ini file
  11. *  than the one used with your web server.
  12. *  If this is the case, please ALSO launch this
  13. *  utility from your web server.
  14. ** WARNING **
  15.  
  16. ** Mandatory requirements **
  17.  
  18.   OK        Checking that PHP version is at least 5.3.2 (5.3.8 installed)
  19.   OK        Checking that the "date.timezone" setting is set
  20.   OK        Checking that app/cache/ directory is writable
  21.   OK        Checking that the app/logs/ directory is writable
  22.   OK        Checking that the json_encode() is available
  23.   OK        Checking that the SQLite3 or PDO_SQLite extension is available
  24.   OK        Checking that the session_start() is available
  25.   OK        Checking that the ctype_alpha() is available
  26.   OK        Checking that the token_get_all() is available
  27.   OK        Checking that the APC version is at least 3.0.17
  28.  
  29. ** Optional checks **
  30.  
  31.   OK        Checking that the PHP-XML module is installed
  32.   OK        Checking that the token_get_all() function is available
  33.   OK        Checking that the mb_strlen() function is available
  34.   OK        Checking that the iconv() function is available
  35.   OK        Checking that the utf8_decode() is available
  36.   OK        Checking that the posix_isatty() is available
  37.   OK        Checking that the intl extension is available
  38.   OK        Checking that the intl ICU version is at least 4+
  39.   OK        Checking that a PHP accelerator is installed
  40.   OK        Checking that php.ini has short_open_tag set to off
  41.   OK        Checking that php.ini has magic_quotes_gpc set to off
  42.   OK        Checking that php.ini has register_globals set to off
  43.   OK        Checking that php.ini has session.auto_start set to off
  44.  
  45. ** Optional checks (Doctrine) **
  46.  
  47.   OK        Checking that PDO is installed
  48.   OK        Checking that PDO has some drivers installed: mysql, sqlite, sqlite2

基本的な警告はなくなりました。これでSymfony2を楽しむ環境が整いましたね!

[vim]vim-quickrunでPHPUnitを動かす

Written by uechoco 8月 31
この記事を読む時間:5くらい

相変わらずvimネタが続きます。最近TDDの勉強を始めています。phpで勉強をしようと思っているのでPHPUnitでやろうと思います。テスト駆動サイクルを素早く回すために、vim-quickrunの導入を検討してみました。

PHPUnit環境を整える

@HIROCASTさんのPHPUnitの環境をつくろう for PHPer #tddbc in Tokyo | Act as Professional – プロとしての行為が参考になります。
シェルのコマンドラインで

  1. phpunit -v

が通れば準備完了です。私の場合は上記記事+αで動きました。go-pearでPEARをインストールしたのですが、OS内にPEARのパスが2箇所出来てしまったので、php.iniの末尾でinclude_pathを調整したら動きました。

  1. ;***** Added by go-pear
  2. include_path=".:/usr/lib/php/pear:/usr/lib/pear/share/pear"                                                                                                                                
  3. ;*****

vim-quickrunをインストールする

私のvimプラグインはvim-pathogenで管理しているので、以下のようなコマンドでインストール出来ました。

[shcode]
cd ~/.vim/bundle/
git clone https://github.com/thinca/vim-quickrun.git
[/shcode]

他にも、直接インストールする方法やvundleを使う方法などがあると思います。

PHPUnit用のvim-quickrun設定を.vimrcに記述

vim-quickrunでPHPUnitを使う設定がVimでPHPUnitをQuickRunする – アインシュタインの電話番号☎に載っています。
基本的にはこの通りに.vimrcを記述すれば動くようです。

しかし、私が試しに、HogeTest.phpというファイルをvimで開いてr(私の環境だと[バックスラッシュ+r])で実行してみたところ、PHPUnitファイルとして認識されず、phpファイルとして実行されてしまいました。上記URLにはファイル名の大文字小文字に関係なく末尾が*test.phpで終わるものをPHPUnitファイルとして認識すると書いてありましたが、私の環境では区別されているようでした。(理由はよくわかりませんww

そこで、以下のように大文字の*Test.phpもPHPUnitファイルとして認識するように.vimrcを修正しました。

  1. "----------------------------------------------------------
  2. " vim-quickrun
  3. " @see http://d.hatena.ne.jp/ruedap/20110225/vim_php_phpunit_quickrun
  4. "----------------------------------------------------------                                                                                                                                      
  5. augroup QuickRunPHPUnit
  6.   autocmd!
  7.   autocmd BufWinEnter,BufNewFile *test.php set filetype=php.unit
  8.   autocmd BufWinEnter,BufNewFile *Test.php set filetype=php.unit
  9. augroup END
  10. " 初期化
  11. let g:quickrun_config = {}
  12. " PHPUnit
  13. let g:quickrun_config['php.unit'] = {'command': 'phpunit'}

すると、先程のHogeTest.phpもPHPUnitファイルとして認識されて、quickrun実行出来るようになりました!

[php]xhprofでプロファイリング

Written by uechoco 11月 17
この記事を読む時間:245くらい

最近話題のプロファイラ拡張のxhprofをインストールしてみます。今更かよってツッコミは なしで。CentOS 5.5に入れます。
[shcode]
# GraphVizをインストール(Dot言語をグラフ化するツール)
sudo yum install -y –enablerepo=epel graphviz graphviz-gd

# 最新版のDL,コンパイル(http://pecl.php.net/package/xhprof)
wget http://pecl.php.net/get/xhprof-0.9.2.tgz
tar zxf xhprof-0.9.2.tgz
cd xhprof-0.9.2/extension/
phpize
./configure
make
sudo make install
# /usr/lib64/php/modules/ディレクトリ内にxhprof.soが入っている

# プロファイルの溜め場所(なくても動いた気がするけど)
sudo mkdir /var/log/xhprof
sudo chmod 777 /var/log/xhprof/

# 設定ファイルの追加
sudo vim /etc/php.d/xhprof.ini

sudo apachectl graceful
[/shcode]

// /etc/php.d/xhprof.iniの内容

  1. [xhprof]
  2. extension=xhprof.so
  3. ;
  4. ; directory used by default implementation of the iXHProfRuns
  5. ; interface (namely, the XHProfRuns_Default class) for storing
  6. ; XHProf runs.
  7. ;
  8. xhprof.output_dir=/var/log/xhprof

Symfony2のapp_dev.phpにXHProfを導入してみます。
[shcode]
# 専用ビューアに必要なファイルをコピーします
cd xhprof-0.9.2/
cp -a xhprof_* /path-to-root/symfony-sandbox/web/
[/shcode]

app_dev.phpを次のように変更します(URLだけ変えてください:私の場合はsymfony-sandbox.localweb02)
[phpcode]
save_run($xhprof_data, $XHPROF_SOURCE_NAME);

// ビューアへのリンク(お好みで)
echo “xhprof Result\n”;
}

xhprof_enable();
register_shutdown_function(‘__xhprof_finish’);

require_once __DIR__.’/../app/AppKernel.php’;

use Symfony\Component\HttpFoundation\Request;

$kernel = new AppKernel(‘dev’, true);
$kernel->handle(new Request())->send();

[/phpcode]

app_dev.phpで適用なページを表示すると、ページの下部にリンクが現れます(下図はBalibaliBlogBundleを動かしたところ)

プロファイル結果はhttp://www.webpagescreenshot.info/img/792351-1116201060526PMです。
GraphVizのpngデータはhttp://labs.uechoco.com/blog/wp-content/uploads/xfprof_balibali_20101117_0100.png.zipで見れます。

[php]配列の最初の要素のキーを取得するスマートな方法

Written by uechoco 10月 20
この記事を読む時間:17くらい

phpで、配列の最初の要素のキーを取得したい時ってありませんか。最初の要素の値だったらarray_shift()で配列壊しながら取得することもできるのですが、最初のキーを取得する関数ってないんですよね。

で、Twitter上で緩募したところ、わりと返信が返ってきました(うれしい!みなさんありがとー)。

Togetter – 「phpで配列の最初の要素のキーを取得するスマートな方法」

どれを使っても取得は出来るんですが、個人的に好きなのは
[phpcode]
reset($data);$name=key($data);
[/phpcode]

[phpcode]
key(array_slice($array, 0, 1));
[/phpcode]
の2つかな。キーを取得できることはもちろんですが、意図が明確な気がして。

みなさんも何か案があれば @uechoco 宛にTwitterで教えてください。上記Togetterのリストを更新させていただきます。