肉とビールとパンケーキ by @sotarok

少し大人になった「肉とご飯と甘いもの」

DotCloud で PHP アプリを設置してみたときの色々

beta の invite もらったので DotCloud で遊んでみました。

アプリ1つくらい設置してみないとなんだかよくわからないよねってことで、とりあえずどこで公開するかなーと思っていた、paste アプリ を設置してみた。


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 されてはじめて設定は適用される。
両者の違いは、

  • nginx.conf : nginx のグローバルな設定
  • fastcgi.conf : location ~ /.+\.php$ に適用される設定

というだけ。これは設定が 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 以下をのぞけばわかることだけど、

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 でマッチできなくなるのでだめだった、というわけです。

その他PHPについて

PHP でも rake みたいなツールがほしい&一般的になってほしい
PHP だとどうしても設置後の task みたいなものを記述できる環境がないので、PaaS の恩恵でデプロイが楽になっても、アプリケーションのセットアップと設定管理みたいなものをどうするかって課題が残る。

今のところ、postinstall でなんかしら設定を行うように書いておくのがまあ普通かなーと思う。

まとめと雑感

  • DotCloud いいかんじ
  • サーバ建てるのとかデプロイとか超楽
    • PaaS的なものが増えてきてほんとアプリケーションエンジニアがインフラ触る機会って今後減ってくるんだろうなーと思った
  • Apache で簡単に動く PHP も好きだけど nginx な環境も最近は割と簡単に揃うし FPM とかも徐々に使われるようになるのかなーと思いつつ、色々な環境で動かせるようにしておくのは、たしかによさそう *2
    • まあいずれにしても Apache べったり、じゃなくてもいい時代かもね
  • DotCloud はその他ミドルウェアも充実してるからいろいろ遊びたいなー
  • あ、ってことで Pastit っていう、設置型ペーストアプリを公開しました。これはもとも Internal な環境で (社内とか) snippet の共有が簡単にできるといいなーと思って作ったので、使いたい方は設置してみるといいかもしれません、結構便利ですよ。まだ embed できないけど。

*1:nginx で Apache の DocumentRoot にあたるものってなんていうの?

*2:他の言語で FastCGIWSGI/PSGI などの動きがあるなか PHPFastCGI

Tiarra に設定を再読込させるための方法

Tiarra といえば、IRC Proxy としてはもはや定番といってもいいとおもうんだけど、それの設定を再読込させたい話。


多分いくつか方法はある。と思う。で、設定を変更するたびに再起動とかやってると、所属してる全チャネルから抜けたり入ったりして鬱陶しいので、起動したまま設定を再読込させたい話。

いや、今さらだけど。
2つ方法あって、どちらでも再読込はできる。

/load する

設定変更後、Tiarra に接続してるクライアントで

/load

と発言する。

そうすれば設定を読み込み直してくれる。便利。

SIGHUP を送る

bot として Tiarra を使ってる場合、IRCクライアントで接続していない場合がある。その場合でも再起動せずに読み込ませたい。そういう場合はSIGHUP送れば良いことに最近気づいた(遅

まず pid 調べて、

% ps aux | grep tiarra
sotaro   18231  0.0  0.6  30304  5992 pts/6    Sl   15:05   0:06 /usr/bin/perl -w ./tiarra --config=tiarra.conf

SIGHUP

% kill -1 18231

大変お行儀のよい子で助かるね、って話。

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:え、この記事もう一昨年記事!?時間進みすぎじゃないすか?

Git で過去にさかのぼってタグ付けする (git tag)

もうだいぶ歴史を進めて開発進めてたんだけど、そういやあのプロトタイプが動いたときタグうっときゃよかったなーなどと思ったんだけど、意外と情報がなかったからメモ。
git-flow 使って、develop で開発進めてたりして、リモート/ローカルで push/pull も頻繁にしてるリポジトリ。現存するのは、develop, master のみ、だいぶ昔に feature/hoge からマージした段階に戻って master にマージしてタグ付けしたい、みたいな要望(割とあるよね?あるよね?

$ git log --all --graph

とか見ながら、コミットオブジェクトのハッシュ確認。例えば、「38fef39」が対象のコミットだとする。

そのハッシュをチェックアウト。

$ git checkout 38fef39
Note: checking out '38fef39'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 38fef39... hogehoge

この時点では、どのブランチにも属していない状態になる。なので、一旦ブランチ切ってそこに入る。releaseブランチを切ってしまおう。

$ git branch -a
* (no branch)
  develop
  master
  remotes/origin/develop
  remotes/origin/master

$ git checkout -b release/0.0.1

masterをチェックアウトして、merge。no fast forward で。

$ git checkout master
$ git merge --no-ff release/0.0.1

で、タグを打つ。

$ git tag -a 0.0.1

あとはブランチ削除

$ git branch -d release/0.0.1

完了!

2011 年もよろしくお願いします

結局例年通り、2010年まとめエントリは間に合いませんでしたが(w)、一応、まとめつつ、今年の目標など。

2010年のまとめ

もう面倒だから箇条書きでまとめますか。思いでエントリー。

  • 1月
    • 修論がんばってた
  • 2月
    • 修論終了、卒業決定
  • 3月
    • 大学院卒業。6年間の学生生活さようなら。
    • 初アメリカ上陸。ベガスに卒業旅行。楽しかった。
    • その他旅行色々した。遊び三昧の月だった。
  • 4月
    • 就職
    • 色々生活が変わった。が、基本的にやってることは変わらなかったかな。
    • とはいえ、色々新しいことと出会って、本当に楽しい出足。
  • 5月
    • 執筆必死。
  • 6月
    • 執筆必死。
  • 7月
    • 執筆必死。
  • 8月
    • 結婚した。
    • そして引越しした。
    • 祝われた。みんな本当にありがとう!!
  • 9月
    • PHPカンファレンス開催。主催側2年目。今年も広報がんばった。あとEthnaネタ発表した。
    • MessagePack Hackathon に参加。その後うまいこと動けず、結果は出せなかったけど。久々にCとか触った。
  • 10月
    • reroomリリース(wozozoが)
    • WEB+DB の特集執筆。人生初の雑誌記事書いた。
  • 11月
    • パーフェクトPHP発売&増刷。人生初の著書が発売された。本当に嬉しかった。そして評判も結構よくて更にうれしかった。
    • 人付き合いに苦しんだw
  • 12月
    • 大事な出会いのあった月。
    • モダンPHP勉強会 #2 開催。
    • reroomで取材&特集リリース。
    • Openpear 2 リリース。ようやく。
    • Ethna 2.6.0 beta1 リリース。まさか2.6.0出すとはね。

12月は盛りだくさんだったなあ。疲れた。4月〜7月くらいなんにも覚えてないなーとおもったら、振り返ったら執筆ばっかりやってたんですね。佳境でした。
まぁ、努力、実ってくれたからよかったかなw


うちの社長が言ってた言葉の中に刺さったものがあって、「毎年年末に、1年前を振り返ってみて、1年前には想像のできなかった自分になっていないとね」って話なんですが、これは良い言葉ですね。
で、今年どうなってたかというと、まあ、色々ありましたが、色々、今年は結構想像を絶する話が色々とあり、想像を超えることができたような気がします。

2011年

色々細かいところまで話すとキリがないけど、いくつか。

  • 仕事:なにか1つ達成する
  • nequal:大暴れする


今年もよろしくお願いします。

名前空間と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 の実装は簡単で、一言で説明すると、

登録された名前空間とパスを結合して \ を / に変えて .php を付ける

これだけです。

では、

$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 さんにお渡しします!

PHP 5.3の話もちゃんと説明している、パーフェクトPHPもよろしくね!

*1:そういや、Google Groupsってページ機能をサポートしなくなるっぽいけど、このへんのページはどうなるんだろうか・・・

*2:file_exists使うとstatが発生するから、コスト的になーってやつがありますよね、まあ、ErrorException飛ばすようにしろよとかいろいろありますが、個人的にはこれでしっくりくるかんじ

モダンPHP勉強会 #2 やります!

今回のテーマは、モダンなテンプレートエンジン Twig です!

パーフェクトPHPの発売&増刷記念トークとサイン会、プレゼントもあるよ!(多分)
みんな参加してね!


パーフェクトPHP (PERFECT SERIES 3)

パーフェクトPHP (PERFECT SERIES 3)