Behat でメソッド定義して正規表現にもマッチしているはずなのに「未定義」と言われてハマった件
原因が分かったときに死ぬほど悲しかったが、ひとまず記録しておく。
... When I run "git daily init" ...
とか書いてて、メソッドも
<?php // ... /* * @When /^I run "git daily(?: ([^"]*))?"$/ * * @param string $args */ public function iRunGitDaily($args = '') // {{{ { // ... }
と定義してあって、これで間違いなく正規表現にマッチしているはずなのに、
未定義のステップを、次のスニペットで実装できます: /** * @When /^I run "([^"]*)"$/ */ public function iRun($argument1) { throw new PendingException(); }
などと言われ、やたらハマってたんだけど、落ちついてドキュメントをもう一度読んでいたら、
Notice the comment block starts with /**, and not the usual /*. This is important for Behat to be able to parse such comments as annotations!
Defining Reusable Actions - Step Definitions — Behat 2 documentation
。。。oh ... なるほど ...
メソッドの定義を
<?php // ... // ↓ここが ** じゃなくて * になってた /** * @When /^I run "git daily(?: ([^"]*))?"$/ * * @param string $args */ public function iRunGitDaily($args = '') // {{{ { // ... }
これで解決。
初歩的?
- (追記) ソースまで追っかけてないけどこの仕様は多分 PHP の ReflectionMethod の getDocComment() メソッドの仕様によるものとおもわれる
pecl install hoge でインストールしたとき、インストール先が extension_dir じゃないディレクトリになってしまう場合
Debain での話。
- 手元で色々 PHP いじってて環境が微妙なことになってしまったとき
- まぁ、こういうことになる人ってあんまいないと思うけど
$ sudo pecl install hoge
でインストールすると、
... checking for PHP includes... -I/usr/include/php -I/usr/include/php/main -I/usr/include/php/TSRM -I/usr/include/php/Zend -I/usr/include/php/ext -I/usr/include/php/ext/date/lib checking for PHP extension directory... /usr/lib/php/20100525-debug checking for PHP installed headers prefix... /usr/include/php ...
おや?
で、当然 /etc/php5/conf.d/http.ini とかに
extension=http.so
とか書いても、
$ php -v PHP Warning: PHP Startup: Unable to load dynamic library '/usr/lib/php5/20090626/http.so' - /usr/lib/php5/20090626/http.so: cannot open shared object file: No such file or directory in Unknown on line 0 ...
とかいわれる。
extension のインストールされたディレクトリと、phpの設定がもってる extension dir が違うようだ。
$ php -i | grep extension_dir extension_dir => /usr/lib/php5/20090626 => /usr/lib/php5/20090626
となるから当然で。
あれ、じゃあなんで pecl install するときにここに入らないのだ、と思い一応 pecl config-show で確認。
$ pecl config-show ... PHP extension directory ext_dir /usr/lib/php5/20090626 ...
ちゃんとなっている。
ビルドのときには /usr/bin/php-config が使われるのでそれも確認
$ which php-config /usr/bin/php-config $ php-config ... --libs [-lcrypt -lz -lcrypt -lonig -lcrypto -lssl -lcrypto -ldb-4.8 -lqdbm -lbz2 -lz -lcrypto -lssl -lcrypto -lrt -lm -ldl -lnsl -lxml2 -lgssapi_krb5 - lkrb5 -lk5crypto -lcom_err -lxml2 -lxml2 -lxml2 -lcrypt -lxml2 -lxml2 -lxml2 -lxml2 -lcrypt ] --extension-dir [/usr/lib/php5/20090626-debug] --include-dir [/usr/include/php5] ...
あれ。
php-config は誰によってインストールされるか、
$ dpkg -S php-config php5-dev: /usr/share/man/man1/php-config5.1.gz php5-dev: /usr/bin/php-config5
php5-dev。だけど /usr/bin/php-config5 ってことは多分 /usr/bin/php-config は symlink にしてるんだな、と確認してみると、
$ ls -la /usr/bin/php-config -rwxr-xr-x 1 root root 4570 2012-02-03 08:36 /usr/bin/php-config
あれ。実体だ。
つまり原因は、
- 何かしらのタイミングで php-config が実体になっちゃった
- php5-dev をインストールしなおしても、update alternatives が働かない
- 何かしらのタイミングで実体になっちゃったものがずっとつかわれちゃってる
- pecl install のときにおかしくなる
ということなので、 /usr/bin/php-config を削除して php5-dev のインストールしなおし
$ sudo rm /usr/bin/php-config $ sudo apt-get remove php5-dev $ sudo apt-get install php5-dev ... php5-dev (5.3.10-1~dotdeb.1) を設定しています ... update-alternatives: /usr/bin/php-config (php-config) を提供するために 自動モード で /usr/bin/php-config5 を使います。 update-alternatives: /usr/bin/phpize (phpize) を提供するために 自動モード で /usr/bin/phpize5 を使います。m
で、再度確認
$ ls -la /usr/bin/php-config lrwxrwxrwx 1 root root 28 2012-02-06 11:47 /usr/bin/php-config -> /etc/alternatives/php-confi $ php-config ... --libs [-lcrypt -lz -lcrypt -lonig -lcrypto -lssl -lcrypto -ldb-4.8 -lqdbm -lbz2 -lz -lcrypto -lssl -lcrypto -lrt -lm -ldl -lnsl -lxml2 -lgssapi_krb5 - lkrb5 -lk5crypto -lcom_err -lxml2 -lxml2 -lxml2 -lcrypt -lxml2 -lxml2 -lxml2 -lxml2 -lcrypt ] --extension-dir [/usr/lib/php5/20090626] --include-dir [/usr/include/php5 ...
OK。
で、 pecl install してめでたし。
PHP Apocalypse で発表してきました #phpapoc
PHP - Be Happy with PHP というタイトルで発表してきました。PHPというか、なんとなく、PHP全般とか、チーム開発とか、開発全般とかの話です。
※スライドだけ見ても全然伝わらない風の発表でしたね
今後の話とか、その他のまとめ
- 今回の発表は、今、僕が感じていることであって、今後僕はまたいろんな壁にあたったり乗り越えたり挫折したりしながらきっと成長していく(つもり)ですし、その時に何考えてるのかはわかりませんし、未来の僕がこのときの自分を振り返ってあの頃はガキだったな、と思うことは多分ありますし、まぁそうでないといけないですし、などなど色々あります。
- なので、そういった意味でも、最初のお断り通り、この話をどう受け取るかは聞いてくださったみなさん次第です。まぁそもそもまとまってないですが。
- @koriyam さんの話はとても共感できるものでした。僕の話まとまらなかったけどとってもまとまっていて素敵な発表でしたね。
- 最後に、いろいろまとめてくれた @tsuyoshikawa、会場運営のカトーくん、会場貸してくださったグリーさん、ありがとうございました。
最近お気に入りのPHPライブラリ開発手法
PEAR2/Pyrus ってどうなったんだっけ?
という話はとりあえず一旦置いておいて、最近わりかしカジュアルにPHPライブラリを開発して配布する方法がなんとなく自分の中で定着してきたので超ざっくりまとめておく。
ソースコードはGitHub、開発にはgitflow、配布はOpenpear
Openpear で世界征服の話はどうなったんだ、というのは置いておいて、ざっくり、上記の通り、
というのが一番楽だと思っている。
ソースコードはGitHub
Openpear はとっても便利なサービスで、SVNでのホスティングもサポートしているのだけど、今更SVNを使いたくもないし、Openpear で今後Gitをサポートする予定もない。
GitHubではGitによるfork/pull request のエコシステムが出来上がりつつあると思っていて、開発者もそこにいるし、GitHubでなんか作っておけば人にコード見てもらったり修正してもらったり、それを取り込んだりが非常にやりやすい環境が揃っている。
もうGitHubを使わない手はない。
配布はOpenpear
で、GitHubで開発したものを直接PEARパッケージ化して、PEARコマンドでインストールできるようにしたいわけだ。
だけど、GitHub登場以来何度かForumに登場しているPEARの公式サポートの件はまとまらないまま話が立ち消えたりして、なんともなっていない。
と、ここで登場するのがOpenpearだ。
Openpear は、上記の通りSVNリポジトリのホスティングはしているが、パッケージの設定で、外部のリポジトリ(SVN、Git、Mercurial)が指定できる。
つまり、ここに、ホスティング先のGitHubのリポジトリを指定すれば、それだけでPEARパッケージ化することができるし、以下のコマンドでインストール可能な形式で配布できる。
$ sudo pear install openpera/Git_Daily
ところで、OpenpaerでGitHubからリリースするにはちょっとした設定のコツ?がいるというか、注意すべき点があるので、一応それはここで説明しておく。
- public にアクセス可能なリポジトリURIを指定すること
- master からリリースされるため、master をリリース可能なバージョンとして作るようにリポジトリ運用をすること
- (Openpear 側に、リリース先のブランチを指定する機能がない...ということです)
- target dir に、リポジトリのルートからの相対パスを指定する
- 例えば、 上記の git-daily であれば、
- ルート: https://github.com/sotarok/git-daily/tree/master
- PEARリポジトリに入れたいディレクトリ: https://github.com/sotarok/git-daily/tree/master/src
- 従って、リリース設定は、 target dir を src とする
- 例えば、 上記の git-daily であれば、
- コマンドラインスクリプトをインストールするときはPEAR_PackageProjector2 の conf 形式で拡張設定をしなければいけない(この話はまた今度書くか)
以下参考:
開発ツールとしてgitflow を使う
gitflow は、上記Openpearの制約にぴったりなツールだ。
develop ブランチを開発用ブランチとして普段のやりとりにつかい、releaseブランチを経て、最終的にタグと共に master ブランチに merge される。master ブランチは、常に、安定してきた開発用のブランチからのマージコミットしか存在せず、つまり、master ブランチが、「配布して良いブランチ」となる。
参考:
- A successful Git branching model » nvie.com
- 見えないチカラ: A successful Git branching model を翻訳しました
- A successful Git branching model を補助する git-flow を使ってみた - Twisted Mind
あ、ちなみに、さっきから例に用いている git-daily というのは、gitflow に似たツールなんだけど、ライブラリ開発用というより、もっと頻繁に修正リリースとかが発生するようなウェブアプリ開発用のgit補助ツールであったりする。最近はいつも自分もコレを使ってウェブアプリの開発をしているわけだけど、これはこれで相当便利なのでまた今度話題にします。
一応参考:
具体的な手順
- 手元で、Gitでライブラリ開発を開始する
- git flow init して、develop で開発
- 適当なタイミングでGitHubにリポジトリを作ってpush
- developブランチでなんやかんやしてよしリリースやー、となったら git flow release start, -> … -> git flow release finish
- master にマージされるので、これをGitHubにpush
- Openpear にそのライブラリ用のパッケージを新規作成
- リポジトリ設定で、GitHubのURIを指定する
- リリース処理
- pear コマンドでインストール可能な形で配布完了
という感じです。あとはGitHubで開発継続、gitflowでmasterへマージ、Openpearで配布(リリース)を繰り返せばなんとなくうまくいく感じがしませんか。
DotCloud で PHP アプリを設置してみたときの色々
beta の invite もらったので DotCloud で遊んでみました。
アプリ1つくらい設置してみないとなんだかよくわからないよねってことで、とりあえずどこで公開するかなーと思っていた、paste アプリ を設置してみた。
- Pastit: http://www.pastit.dotcloud.com/
- Pastit GitHub: https://github.com/sotarok/pastit/
DotCloud の PHP の環境は、Ubuntu 上の nginx + php5-fpm (PHP 5.3.2) が標準で、なんというか時代の流れを感じました。いいね!
のは、まあいいとして、まぁ PHP アプリなんてものはたいてい Apache で動くことが前提とされてるもので (そうなのか? いや、そうだと思う。それが PHP のメリットだし) nginx な環境とか全然考慮してなかった + そんな PHP ユーザな弊害として nginx 力が低すぎてアレだったのですこしばかりはまったので、まあそのあたりのメモとして。
ということで、話は Pastit のディレクトリ構成を前提に進めますが、GitHub にあがってるので参考にしてください。
主な話の内容は、
- DotCloud のアプリ設置操作
- DotCloud の環境とか
- DotCloud + PHP + nginx の環境とか
とりあえず設定
まあ Tutorial で言われてるとおりコマンドをインストールして、アプリケーションサーバと MySQLサーバをつくってみる。
最初に dotcloud コマンドを使ったときに token をセットしろっていわれるけどこれはログインして setting を見たときに書いてあるやつな。
$ dotcloud list $ dotcloud create pastit $ dotcloud deploy --type php pastit.www $ dotcloud deploy --type mysql pastit.mysql
MySQL の設定とかは以下で確認
$ dotcloud info pastit.mysql cluster: wolverine config: mysql_password: **** created_at: 1304535584.4373901 name: pastit.mysql namespace: pastit ports: - name: mysql url: mysql://root:****@mysql.pastit.dotcloud.com:port - name: ssh url: ssh://dotcloud@mysql.pastit.dotcloud.com:port state: running type: mysql
MySQL は、以下でログインできるので、これでログインして DB 作ったりユーザ作ったりする。
$ dotcloud run pastit.mysql -- mysql -uroot -p\'****\'
で、ソースコードのデプロイ。今回は Git リポジトリです。Pastitのリポジトリのルートで実行。Gitのブランチを指定する場合は -b で。しなければ master が push される。
$ dotcloud push -b develop pastit.www .
で、http://www.pastit.dotcloud.com/ で動けばOK。今回は pastit.www という名前でデプロイしてるからこのURLになるけど、pastit.* に当たる部分が URL のサブドメイン *.pastit.dotcloud.com になる。
前提として知っておくべき DotColud の環境
push するとなにがどう設置されるか
DotCloud に push すると、以下のようなディレクトリ構成で設置される。とりあえず type = PHP の場合の話だが、アプリケーションサーバの場合他のタイプでも大差ないだろう。
dotcloud@pastit-www:~$ pwd /home/dotcloud dotcloud@pastit-www:~$ ls -la -rw-r--r-- 1 dotcloud www-data 54722 2011-05-06 18:53 build.log drwxr-xr-x 13 dotcloud www-data 4096 2011-05-06 18:53 c1bda3a lrwxrwxrwx 1 root root 7 2011-05-06 18:53 code -> c1bda3a lrwxrwxrwx 1 dotcloud www-data 8 2011-05-06 18:53 current -> code/www
この c1bda3a ってやつがコードの実態。
今回の場合リポジトリが Git だったので Git でのデプロイした時のコミットオブジェクトの hash 値になっている。
code ってやつは、常に最新のソースのルートへの symlink になっている。
current ってやつも symlink で、ここがアプリケーションの root (ドキュメントルート的な) の場所を指し示す。ここは、特に何も設定しなければ、 code と同じ場所を指すが、次に説明する dotcloud_build.yml で設定することで変更できる。
capistrano とかを使ったことがあれば、ソースコードが配置されて、最新のツリーへの symlink があって、みたいな構成はだいたい同じなのでキャッチアップしやすいと思う。
dotcloud_build.yml
DotCloud にアプリを push をすると、そのディレクトリをドキュメントルート *1 として設置される。多くのフレームワークのアプリの場合これは好ましくないはず。
なので、そのへんを設定してあげるのが docloud_build.yml。ここに、アプリの root がどこなのかという設定を記述できる (その他の設定項目もあるがとりあえずは省略) 。
このファイルは push するディレクトリのルートにおいておく。今回は approot として www ディレクトリを指定。
www: approot: www
nginx.conf と fastcgi.conf
approot 以下においておくと、読み込んでくれる nginx の設定。
といっても、Apache の .htaccess とはちがって、単に nginx の設定で include されてるだけだから、pushしたときに nginx が reload されてはじめて設定は適用される。
両者の違いは、
というだけ。これは設定が include される場所が違うだけ。PHP に関する設定は fastcgi.conf にしておくのがよさそう。
nginx.conf と fastcgi.conf は、deny 設定されてたのに、次に説明する postinstall だけ deny されてなかったので、それを nginx.conf に書いておいた。あと try_files の設定 (後述)。
try_files $uri $uri/ /index.php?$args; location = /postinstall { deny all; }
postinstall
いかにも Debian 好きっぽそうなこの名前は、approot 以下に実行ファイルとしておいておくと、push の最後に実行してくれる。アプリケーションの設定の適用を行うファイルとかを適切においておけば良い。
ちなみに、Pastit は Ethna の config を home においておいて、それを push 時に適切なディレクトリにコピーするようにしている。PHP厨らしくちゃんとPHPで記述しておく。
#!/usr/bin/php <?php define('DOTCLOUD_HOME', '/home/dotcloud/'); $files = array( 'pastit-ini.php' => 'code/config/pastit-ini.php', 'pastit-app-ini.php' => 'code/config/pastit-app-ini.php', ); foreach ($files as $origin_file => $dest_file) { if (!file_exists(DOTCLOUD_HOME . $dest_file) && file_exists(DOTCLOUD_HOME . $origin_file)) { echo DOTCLOUD_HOME . $origin_file, " -> ", DOTCLOUD_HOME . $dest_file, PHP_EOL; copy(DOTCLOUD_HOME . $origin_file, DOTCLOUD_HOME . $dest_file); } } // task echo 'end of task', PHP_EOL;
その他
まあ ssh で入って /etc/nginx 以下をのぞけばわかることだけど、
- favicon.ico は current/static/favicon.ico (approot/static/favicon.ico) を参照される
- robots.txt も current/static/robots.txt を参照される
nginx x PHP な設定
PAHT_INFO とか
PATH_INFO使ってる人ってどれだけいるのかわからないけど Ethna の UrlHandler はそれ使って path 解析するから、あるといい。
んだけど、PHP で一般的に知られている nginx 向けの PATH_INFO の設定だと、location が
/.+\.php.+$
にマッチするのが前提だったりしなくもないので、それだと、DotCloud 的にはよろしくない。というのは、DotCloud の PHP 向けの nginx の設定が
location ~ /.+\.php$ { if ( -f /home/dotcloud/current/maintenance) { return 503; } try_files $uri /static/404.html; fastcgi_pass unix:/var/dotcloud/php5-fpm.sock; include fastcgi_params; include /home/dotcloud/current/*fastcgi.conf; }
となっているので、index.php/hoge とかにマッチしてくれない。
ならば /.+\.php.+$ にマッチする設定を approot/nginx.conf に書いておけば良いのかもしれないけど、fastcgi_pass のような DotCloud 環境依存な設定がそっちに記述されるのもどうかなと思うので、ここはゴリ押しで解決させておく。
index.php の先頭でこんなかんじで PATH_INFO のセットをしておいた
<?php if (!isset($_SERVER['PATH_INFO'])) { $request_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; if (!empty($request_uri)) { $parsed_uri = parse_url($request_uri); $request_uri = isset($parsed_uri['path']) ? $parsed_uri['path'] : ''; } $_SERVER['PATH_INFO'] = $request_uri; }
rewrite とか
このあたりは DotCloud の Tutorial にあるように、try_files で設定しておくのがよさそう。$args も忘れずに。
try_files $uri $uri/ /index.php?$args;
よくある nginx.conf の設定だとここで
/index.php$requrest_uri
などをしておくことで PATH_INFO は設定するんだけど、そもそもこれは前述のとおり location でマッチできなくなるのでだめだった、というわけです。
まとめと雑感
- DotCloud いいかんじ
- サーバ建てるのとかデプロイとか超楽
- PaaS的なものが増えてきてほんとアプリケーションエンジニアがインフラ触る機会って今後減ってくるんだろうなーと思った
- Apache で簡単に動く PHP も好きだけど nginx な環境も最近は割と簡単に揃うし FPM とかも徐々に使われるようになるのかなーと思いつつ、色々な環境で動かせるようにしておくのは、たしかによさそう *2
- まあいずれにしても Apache べったり、じゃなくてもいい時代かもね
- DotCloud はその他ミドルウェアも充実してるからいろいろ遊びたいなー
- あ、ってことで Pastit っていう、設置型ペーストアプリを公開しました。これはもとも Internal な環境で (社内とか) snippet の共有が簡単にできるといいなーと思って作ったので、使いたい方は設置してみるといいかもしれません、結構便利ですよ。まだ embed できないけど。
PEAR で Unable to find the wrapper "channel" - did you forget to enable it when you configured PHP?
なんか出るよね最近。環境はDebian SqueezeのPHP 5.3.x。いや Lenny + dotdeb でも出る。(てか後述のとおり xdebug の設定だから環境依存じゃないかも。ちなみに PEAR は version 1.9.1)
検証してないけど channel:// を register してないのに使ってる気がする。
$ sudo pear upgrade-all PHP Warning: file_exists(): Unable to find the wrapper "channel" - did you forget to enable it when you configured PHP? in /usr/shar e/php/PEAR/Downloader/Package.php on line 1517 PHP Stack trace: PHP 1. {main}() /usr/share/php/pearcmd.php:0 PHP 2. PEAR_Command_Common->run($command = 'upgrade-all', $options = array (), $params = array ()) /usr/share/php/pearcmd.php:305 PHP 3. PEAR_Command_Install->doUpgradeAll($command = 'upgrade-all', $options = array (), $params = array ()) /usr/share/php/PEAR/Command/Common.php:271 PHP 4. PEAR_Command_Install->doInstall($command = 'upgrade-all', $options = array (), $params = array (0 => 'channel://components.ez.no/base', 1 => 'channel://components.ez.no/consoletools', 2 => 'channel://openpear.org/phpman', 3 => 'channel://pear.ethna.jp/smarty', 4 => 'channel://pear.ethna.jp/simpletest', 5 => 'channel://pear.ethna.jp/ethna', 6 => 'channel://pear.php.net/structures_graph', 7 => 'channel://pear.php.net/console_getopt', 8 => 'channel://pear.php.net/archive_tar', 9 => 'channel://pear.php.net/pear', 10 => 'channel://pear.php.net/xml_util', 11 => 'channel://pear.php.net/db', 12 => 'channel://pear.phpunit.de/dbunit', 13 => 'channel://pear.phpunit.de/file_iterator', 14 => 'channel://pear.phpunit.de/php_codecoverage', 15 => 'channel://pear.phpunit.de/phpunit', 16 => 'channel://pear.phpunit.de/phpunit_selenium', 17 => 'channel://pear.phpunit.de/php_timer', 18 => 'channel://pear.phpunit.de/text_template', 19 => 'channel://pear.phpunit.de/php_tokenstream', 20 => 'channel://pear.phpunit.de/phpunit_mockobject', 21 => 'channel://pear.symfony-project.com/yaml', 22 => 'channel://pear.twig-project.org/twig', 23 => 'channel://pecl.php.net/memcached', 24 => 'channel://pecl.php.net/xdebug')) /usr/share/php/PEAR/Command/Install.php:900 PHP 5. PEAR_Downloader->download($params = array (0 => 'channel://pear.php.net/structures_graph', 1 => 'channel://pear.php.net/cons ole_getopt', 2 => 'channel://pear.phpunit.de/php_codecoverage', 3 => 'channel://pear.phpunit.de/phpunit', 4 => 'channel://pear.phpunit.de/phpunit_selenium', 5 => 'channel://pear.phpunit.de/phpunit_mockobject', 6 => 'channel://pear.twig-project.org/twig')) /usr/share/php/PEAR/Command/Install.php:661 PHP 6. PEAR_Downloader_Package->initialize($param = 'channel://pear.twig-project.org/twig') /usr/share/php/PEAR/Downloader.php:278 PHP 7. PEAR_Downloader_Package->_fromFile($param = 'channel://pear.twig-project.org/twig') /usr/share/php/PEAR/Downloader/Package.php:160 PHP 8. file_exists('channel://pear.twig-project.org/twig') /usr/share/php/PEAR/Downloader/Package.php:1517 .. 延々と upgrade-all ok: channel://pear.php.net/Structures_Graph-1.0.4 upgrade-all ok: channel://pear.php.net/Console_Getopt-1.3.0 upgrade-all ok: channel://pear.phpunit.de/PHP_CodeCoverage-1.0.3 upgrade-all ok: channel://pear.phpunit.de/PHPUnit_Selenium-1.0.2 upgrade-all ok: channel://pear.phpunit.de/PHPUnit_MockObject-1.0.4 upgrade-all ok: channel://pear.twig-project.org/Twig-1.0.0RC1 upgrade-all ok: channel://pear.phpunit.de/PHPUnit-3.5.8 Warning: unlink(/tmp/glibctestQDzKXJ): No such file or directory in System.php on line 206 PHP Warning: unlink(/tmp/glibctestQDzKXJ): No such file or directory in /usr/share/php/System.php on line 206 PHP Stack trace: PHP 1. _PEAR_call_destructors() /usr/share/php/PEAR.php:0 PHP 2. call_user_func_array(array (0 => 'System', 1 => '_removeTmpFiles'), array ()) /usr/share/php/PEAR.php:774 PHP 3. System::_removeTmpFiles() /usr/share/php/PEAR.php:0 PHP 4. System::rm($args = array (0 => '-r', 1 => '/tmp/glibctestQDzKXJ')) /usr/share/php/System.php:440 PHP 5. unlink('/tmp/glibctestQDzKXJ') /usr/share/php/System.php:206
ああ、
1523 $this->_explicitGroup = true; 1524 } 1525 } 1526 1527 if (@is_file($param)) { 1528 $this->_type = 'local'; 1529 $options = $this->_downloader->getOptions(); 1530 if (isset($options['downloadonly'])) { 1531 $pkg = &$this->getPackagefileObject($this->_config, 1532 $this->_downloader->_debug);
@ 使ってるけど、僕の環境は xdebug.scream が On になってるから出るのね。PEARってほんとつくづく気持ち悪いよね。結構大きくなっててもう変えるのも直すのもだるいって話なんでしょうか(どこかで聞いたような話)。PEAR2に期待ってことですかね。。
エラーは出るけど @ つけて見えなくしてるから別にいいよねってかんじなんでしょうか。そんなやつは一度アレ *1を読みなおしてくると良いと思います。
追うの面倒だし、一応インストールはできてるから無視してる、が、気持ち悪い。
(とつぶやくだけのエントリーです。解決を期待した方すみません)
*1:え、この記事もう一昨年記事!?時間進みすぎじゃないすか?
名前空間とautoload、標準的なClassLoaderの実装 (#phpadvent2010)
ってことで、アドベントカレンダーが回ってきたので書きます。なに書こうか迷いましたが、とりあえず、最近自分でも真面目に使い始めたPHP 5.3向けのClassLoaderと名前空間についての話です。
世の中的にずいぶん「これからはPHP 5.3だよね」的な流れがきているので、名前空間の区切りと、ディレクトリ構成、ファイル名、クラス名など、これから書くならどうするんだろ?ってところについておさらいしておきます。
PHP Standards Working Group
第1回のモダンPHP勉強会で、id:Fivestarが発表したように、Symfonyなどの開発者が集まって、このような内容をPHP界隈でちゃんと取り決めて標準っぽくしようよって話をしている、PHP Standards Working Groupというグループがあります。(最近あんま動きがないな)
このグループで議論がまとまっているものに、PSR-0 Final Proposal があり、この中で、名前空間の使い方・クラス名・ディレクトリの区切りはこうしよう、という意見がまとまってます。*1
例をあげると、以下のような感じ。
- \Doctrine\Common\IsolatedClassLoader => /path/to/project/lib/vendor/Doctrine/Common/IsolatedClassLoader.php
- \Symfony\Core\Request => /path/to/project/lib/vendor/Symfony/Core/Request.php
- \Zend\Acl => /path/to/project/lib/vendor/Zend/Acl.php
- \Zend\Mail\Message => /path/to/project/lib/vendor/Zend/Mail/Message.php
ちなみにこのページはid:hnwさんが翻訳されています。
SplClassLoader を使ってみよう!
で、本題。標準的な名前空間とクラス名などの命名規則が決まれば、標準的なAutoloadクラスが作れるよねってことで作られたのが、SplClassLoaderです。SPLって名前が付いてるけど、別に組み込みでもないし、PHP的に標準になっているわけでもないので、「提案」レベルのものですが、SymfonyもこれをベースにClassLoaderをもっているし、まあ実質標準的なものであると考えてもらって問題ないです。
ってことで、これを使ってみましょう。
標準的なディレクトリ構成とクラスのファイルを用意
今回は、以下のような感じで作ってみました。
% tree . |-- Hoge | |-- Hogege.php | `-- Hogera | `-- Aho.php |-- SplClassLoader.php |-- entry_point.php `-- libs `-- vendors `-- Fuga `-- Foo.php
entry_point.php が実際にライブラリを使う側のファイル、Hoge と Fuga が、読み込まれる側のライブラリファイルだと想定し、Fuga はどこか変なとこにディレクトリがあるものだとします。まあ、よくある構成だはと思います。Aho.php Hogera.php Foo.php には実際にクラスが定義されていて、とりあえずコンストラクタが呼ばれると自分自身の名前空間とクラス名を出力するようにしてあります。
例えば次のような感じ:
<?php namespace Hoge\Hogera; class Aho { public function __construct() { echo __CLASS__, PHP_EOL; } }
実際に使ってみる
entry_point.php では、次のよう register() メソッドを呼び出し、名前空間とディレクトリのルートを指定したものを登録します。
<?php require 'SplClassLoader.php'; $class_loader_hoge = new SplClassLoader('Hoge', __DIR__); $class_loader_hoge->register(); $class_loader_fuga = new SplClassLoader('Fuga', __DIR__ . '/libs/vendors'); $class_loader_fuga->register(); $a = new Hoge\Hogege(); $b = new Hoge\Hogera\Aho(); $c = new Fuga\Foo();
この出力は、
Hoge\Hogege Hoge\Hogera\Aho Fuga\Foo
となります。
正しくHogeやFugaの名前空間のクラスが読み込まれましたね。
ClassLoaderの動きを追ってみる
ClassLoader の実装は簡単で、一言で説明すると、
これだけです。
では、
$class_loader_hoge = new SplClassLoader('Hoge', __DIR__); $class_loader_hoge->register();
この設定でAutoloadの設定をし、
$b = new Hoge\Hogera\Aho();
こう使ったときに、どう読み込まれるかというと、
<?php // 以下は、SplClassLoaderのロード部分の実装。 public function loadClass($className) { if (null === $this->_namespace || $this->_namespace.$this->_namespaceSeparator === substr($className, 0, strlen($this->_namespace.$this->_namespaceSeparator))) { $fileName = ''; $namespace = ''; if (false !== ($lastNsPos = strripos($className, $this->_namespaceSeparator))) { // 名前空間区切りが見つかれば、それを最後の名前空間区切り以下をクラス名として、 // クラス名と名前空間に分解する $namespace = substr($className, 0, $lastNsPos); // => "Hoge\Hogera" $className = substr($className, $lastNsPos + 1); // => "Aho" $fileName = str_replace($this->_namespaceSeparator, DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; // 名前空間の区切り文字 "\" を "/" に変換して、パス名にする // => "Hoge/Hogera/" } $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . $this->_fileExtension; // クラス名のアンダースコア "_" を "/" に変換して、拡張子をつける // そしてそれを、パス名に結合し、これをファイル名とする // => "Hoge/Hogera/Aho.php" require ($this->_includePath !== null ? $this->_includePath . DIRECTORY_SEPARATOR : '') . $fileName; // 最後に、設定してあるインクルードパスにファイル名を結合してrequireする // => "/home/sotarok/tmp/splclassloader/Hoge/Hogera/Aho.php" } }
だいたいこんなかんじで、SplClassLoaderのコンストラクタに指定したインクルードパスと、それ以下のパスが名前空間を含んだクラス名とマッピングされ、クラスが読み込まれます。実に単純ですね。
まんまでも使えるけど・・・
実際には、もうちょっとルーズに使いたかったので、registerNamespace()とかのstaticメソッドを定義して使ってます。*2
こうすると、
<?php ClassLoader::registerNamespace('Hoge', '/path/to/Hoge'); ClassLoader::registerNamespace('Fuga', '/path/to/Fuga');
なんてかんじでルーズに使えて素敵です。
というわけで
いよいよ、と思って名前空間を使いはじめると、まだまだノウハウが共有されていなくて(それでも出てきたほうですが)悩むことも多いと思いますが、参考になれば幸いです。
アドベントカレンダーのバトンは slumbers99 さんにお渡しします!