Posted by & filed under 未分類.

PHP_CodeSniffer

PEARにPHP_CodeSnifferというコーディング規約チェッカーがあります。

PHP_CodeSniffer は PHP5 用のスクリプトで、PHP や JavaScript そして CSS のコードについて構文解析や “sniff (クンクンにおいを嗅ぐこと)” を行い、 コーディング規約に反するところを検出します。 開発者には不可欠であろうこのツールを使用することで、 あなたのコードをきれいで一貫性のあるものにできます。 また、開発者がおかしがちな間違いを防ぐ助けにもなります。

PEAR, Zend規約

デフォルトでPEAR, Zend等の規約が入ってるのですがこれがイマイチ使い勝手がよくありません。具体的にはPEARの規約はコメントのタグのチェックが中心です。1 Zendは規約があまり定義されてなく、これら2つの規約は肝心のコードをあまりチェックしてくれません。

Squiz規約

このライブラリを作成したSquiz Laboの規約が最も詳細にチェックしてくれるのですが、あまりに規約が細かすぎ&こだわり過ぎで&独自規約で山のようにエラーが出てしまいます。肝心なものが埋もれこれも使い勝手が良くありません。

modernphp規約

PHP_CodeSnifferは規約ルールセットを定義したXMLを用意する事で、コード規約をカスタマイズする事ができます。そこで定義済みの規約の中から現在のPHPコーディングで一般的だと思われる規約をピックアップして、使いやすいものを用意しようというのが今回のmodernphp規約です。自分の好みに寄らず、現在のPHPコーディングでより標準的なものはどういうものかという視点で調整しました。

これは中々難しい作業です。現在の有名ライブラリやフレームワークに共通したコーディングスタイルがあるのはあると思うのですが、すべてに通るようなものならせっかく大量に用意された規約のほとんどを使わない事になります。しかしユルユルにしたのでは規約の意味があまりありません。2

そこで大量の規約のオンオフがやりやすい形にして、そこから調整する方法はどうかと思いました。まずは全ての規約をオンにして、そこから排除する規約を最も細かい規約単位でオフにしていきます。

インストール

GitHub: phpcs-modernphpを直接、またはフォークして使います。

$ pear install PHP_CodeSniffer
$ cd /path/to/PEAR/PHP/CodeSniffer/Standards
$ git clone git://github.com/koriym/phpcs-modernphp.git modernphp
$ phpcs --config-set default_standard modernphp

※PEARディレクトリがわからないときはこれで表示されます。

$ pear config-show | grep php_dir

調整方法

-sオプションで使います。

$ phpcs -s /path/to/src --standard=modernphp

不要なものを削る時

例えば以下のエラーは「条件によっては読み込まないファイルはrequireではなくてincludeで読み込む」というエラーです。

 218 | ERROR | File is being conditionally included; use "include_once" instead
     |       | (PEAR.Files.IncludingFile.UseIncludeOnce)

これが不要ならrulseset.xmlを編集して以下のように不使用を指定します。

<rule ref="PEAR.Files.IncludingFile.UseIncludeOnce"><severity>0</severity></rule>

modernphpで削除されてるものを加える時

phpcs規約を指定して沢山出るエラーから目的のものを見つけ、ruleset.xmlに追加します。

$ phpcs -s /path/to/src --standard=phpcs

※これで出ない規約は自分で作成するしかありません。

XMLの設定方法はSauiz Laboのruleset.xml 解説ページが参考になります。

規約を公開する

PHPUnitの作者として有名なSebastian Bergmann氏は自分のコーディング規約をSebastian.規約としてGitHubで公開してます。

氏のように自分はこの規約にしたがってコーディングしてると宣言的に公開するのもまた面白いのではないでしょうか。


  1. PEARライブラリ用にかなり多くのphpdocタグを要求します。 []
  2. また主流はどちらか議論の別れるものもあります。例えばprivate / protecedの変数は_(アンダースコア)を接頭子として使うのがPHP4時代からPEAR規約として標準的でしたが、名前で型を表すのは?という議論もあり、多くの開発者がその規約を離れているような感じがします。 []

Posted by & filed under PHP.

現在PHPデベロッパチームはPHP5.4の正式リリースに向けて準備をしていますが、テストを行い結果を送信することで開発協力する事ができます。コーディングも英語も必要ありません。数分で完了します。

ダウンロードとテスト

PHPのソースをダウンロードしてテストを行います。

$ cd /tmp
$ wget http://snaps.php.net/php5.4-latest.tar.gz
$ tar xzvf php5.4-latest.tar.gz
$ cd php5.4-*
$ ./buildconf
$ ./configure
$ make
$ make test

これで数分かけて8000以上のテストが走ります。
テストが一部失敗すると以下のように出ます。

=====================================================================
FAILED TEST SUMMARY
---------------------------------------------------------------------
ZE2 A class constructor must keep the signature of all interfaces [tests/classes/ctor_in_interface_02.phpt]
SPL: DirectoryIterator test getGroup [ext/spl/tests/DirectoryIterator_getGroup_basic.phpt]
SPL: DirectoryIterator test getOwner [ext/spl/tests/DirectoryIterator_getOwner_basic.phpt]
Bug #39322 (proc_terminate() loosing process resource) [ext/standard/tests/general_functions/bug39322.phpt]
Bug #49936 (crash with ftp stream in php_stream_context_get_option()) [ext/standard/tests/streams/bug49936.phpt]
Bug #32001 (xml_parse*() goes into infinite loop when autodetection in effect), using UTF-* [ext/xml/tests/bug32001.phpt]
=====================================================================

You may have found a problem in PHP.
This report can be automatically sent to the PHP QA team at
http://qa.php.net/reports and http://news.php.net/php.qa.reports
This gives us a better understanding of PHP's behavior.
If you don't want to send the report immediately you can choose
option "s" to save it.  You can then email it to qa-reports@lists.php.net later.
Do you want to send this report now? [Yns]:

このレポートを送りますか?と質問されるのでYと答えメールアドレスを入力するとレポートを開発チームに送る事ができます。
自動送信に失敗する場合は表示されるメールアドレスに手動で送信します。

Thank you for helping to make PHP better.

レポートを送ると最後に出てくるメッセージです。

Posting to http://qa.php.net/buildtest-process.php

Thank you for helping to make PHP better.

送信結果はこのページに反映されるはずです。http://qa.php.net/reports/
お疲れさまでした。

※さらなるテスト協力はRamsusさん作成のインストラクションに詳細があります。


Posted by & filed under BEAR, PHP.

Aura.Di

Ray.DiはAura.Diを使用しています。AuraはPHP5.3用フレームワークで、Paul M.Jones.氏がリードのPHP5.2用フレームワークSolarPHPの現在のメジャーバージョンです。有名なフレームワークでは無いかもしれませんが、ライブラリファースト、コンパクトでクリーンなコード、100%テストカバレッジ等、リファレンスとすべき多くの点があるのではと思います。

Ray.Diは基本的にはアノテーションベースのDIコンテナですが、アノテーションを全く使わないAura.Diの上に構築されています。なのでどちらの方法でも依存性の注入を行う事ができます。前回の記事ではアノテーションを使った方法だけ紹介しましたが、この記事では両方の方法を紹介してそれぞれ比較したいと思います。

まずはそのどちらも使えるインジェクターの生成からです。

インジェクターの生成

Containerクラスのインスタンスと、インターフェイスとクラスを紐付けるモジュールの二つを引き数に取ります。ContainerクラスにはForge、ForgeにはConfig、ConfigにはAnnotationインスタンスが必要です。

$di = new Injector(new Container(new Forge(new Config(new Annotation))), new AppModule);

あるいは、

instance.php

require_once  '/path/to/Ray.Di/src.php';
return new Injector(new Container(new Forge(new Config(new Annotation))), new AppModule);

このようにincludeを使って

$di = include '/path/to/scripts/instance.php';

インスタンスをスクリプトから代入します。

クリーンな依存関係

使用される全てのクラスがインターフェイスを持ち、それぞれ必要とされるクラスのコンストラクタで受け取っています。固定化されたクラス関係は存在せずクラスの依存関係はクリーンで、ユーザー作成のコンポーネントとも入れ替え可能です。DIコンテナが扱うクラスだけでなく、DIコンテナそのものも実装(実クラス)ではなく、インターフェイスでつながれています。1

コンストラクタ・インジェクトション

Ray.Diはコンストラクターインジェクションとセッターインジェクション(メソッドを使ったインジェクション)をサポートします。2。3rdパーティのものや既存のライブラリ等、アノテーションが使えない場合の方法と使う方法を別にして紹介します。

ターゲットクラス

ターゲットになるクラスです。ListerクラスのコンストラクタにFindインターフェイスを実装したインスタンス(Finder)を渡す必要があります。

namespace MovieApp {
    class Lister {
        public $finder;
        public function __construct(Find $finder){
            $this->finder = $finder;
        }
    }
    class Finder implements Find {}
    interface Find{}
}

アノテーションを使わないコンストラクタ・インジェクション

イーガーセット

    $di = include __DIR__ . '/scripts/instance.php';
    $di->getContainer()->params['MovieApp\Lister'] = array(
       'finder' => new MovieApp\Finder
    );
    $lister = $di->getInstance('MovieApp\Lister');

params[クラス名]として、ネームドパラメーター3 で引き数を指定します。この準備は通常アプリケーションのブート時等に1度だけ行います。getInstance()時にはコンストラクタ引き数を指定していませんが、”予約”した方法で引き数が渡されインスタンスが生成されます。

レイジーセット

    $di = include __DIR__ . '/scripts/instance.php';
    $di->getContainer()->params['MovieApp\Lister'] = array(
        'finder' => $di->getContainer()->lazyNew('MovieApp\Finder')
    );
    $lister = $di->getInstance('MovieApp\Lister');

イーガーセットでは準備の段階で引き数に必要なインスタンスを生成しましたが、もしかしたら使わないかも、あるいは準備時にはまだインスタンスが確定できないものはlazyNewというメソッドを使ったレイジーセットが行えます。インスタンスの代わりにインスタンスの生成方法をセットしておいてgetInstance()時に遅延実行されコンストラクタ引き数として渡されます。引き数1つめにクラス名、2つ目に引き数をネームドパラメーターで指定します。

クラス同様、コンストラクタインジェクションの設定も親クラスから小クラスに継承されます。つまり、Finderクラスを継承した子クラスの取得時にも適用されます。またgetInstance()の第二引き数でインスタンス取得時に、設定した引き数を指定したパラメーターだけ上書きすることができます。4

アノテーションを使うコンストラクタ・インジェクション

ターゲットのメソッドに@Injectアノテーションでマークします。Ray.Diにインスタンスを代入しなければならない事が伝わります。

namespace MovieApp {
    class Lister {
        public $finder;
        /**
         * @Inject
         */

        public function __construct(Find $finder){
            $this->finder = $finder;
        }
    }
    class Finder implements Find {}
    interface Find{}
}

AbstractModuleを継承したモジュールのconfigureメソッド内でインターフェイスと実クラスを指定します。AbstractModuleにはインターフェイスとクラスを結ぶ様々なメソッドがあり、英語表現のようなDSL5 でインターフェイスとクラスを紐づけます。

    class Module extends \Ray\Di\AbstractModule
    {
        public function configure()
        {
            $this->bind('MovieApp\Find')->to('MovieApp\Finder')->in(Scope::SINGLETON);
        }
    }

前回の記事ではインスタンスを直接してしましたが、この例では実クラスを指定してin()でそのクラスはシングルトンスコープで利用されるように指定しています。二回目以降の注入には同じインスタンスが再利用されます。

    $di->setModule(new Module);
    $lister = $di->getInstance('MovieApp\Lister');

そのモジュールをセットしたインジェクターでインスタンスを取得します。

Conclusion

アノテーションを使用しないでクラス名やメソッド名を指定してそこの何を入れるかを指定する方法と、アノテーションを使ってインジェクトするポイントを指定しインターフェイスとクラスをワイアリングする方法と、依存オブジェクトの2つの指定の方法、Ray.Diはそのどちらも可能という事を見てきました。前者はXMLやYAMLファイルなどのスタティックな設定を持つことが多く、Symfomy2やFlow3、Ding等はこの方式です。何処で注入するかと場所に注目して指定する方法と、何が注入されるかに注目する指定する方法、の2つとも言えないでしょうか。6

* サンプル https://github.com/koriym/Ray.Di/tree/annotation/doc


  1. InjectorとAnnotation以外は全てAuraのコンポーネントです。Configだけ一部機能追加してますが他のクラスはAura.Diそのままです。 []
  2. 現在プロパティインジェクションは実装されていません []
  3. 引き数を順番ではなく変数名で指定 []
  4. $host, $id, $passを引き数に取るようなコンストラクタでgetInstance($class, array(‘host’ => $host);と指定すると$id, $passはデフォルトの値で$hostだけを指定できます。 []
  5. Guiceでこのように表現されてました []
  6. 個人的には前者はコンテナやコンパイルなど実装の都合から生まれた方法で、後者はインターフェイス指向をより意識した方法ではないかと思うのですがどうでしょうか []

Posted by & filed under BEAR, PHP, フレームワーク.

Ray.Di

Ray.Di は DI (Dependency Injection: 依存性注入) のためのフレームワークです。Google Guiceにインスパイアされ、Aura.Diを利用したPHP用DIコンテナです。メソッドインターセプターによるアスペクト指向プログラミングをサポートします。

この記事は初学者向けのDIやAOPの解説は含みませんが、1サンプルを通じてなるべく分かりやすく全体構成を説明したいと思います。

ターゲットオブジェクト

インジェクト対象となるメソッドに@Injectとマークします。@PostConstuctはインスタンスコンストラクトされ後の初期化メソッドを表します。@Transactional, @Templateはユーザーが定義したアスペクト指向プログラミングのためのアノテーションで、@Aspectと共に用い、そのメソッドがインターセプトされる事を指定します。

/**
 * @Aspect
 */

class User
{
    private $db;
   
    /**
     * @Inject
     * @Named("pdo=pdo_user")
     */

    public function __construct(\PDO $pdo)
    {
        $this->db = $pdo;
    }

    /**
     * @PostConstruct
     */

    public function init()
    {
        // if not exist...
        $this->db->query("CREATE TABLE User (Id INTEGER PRIMARY KEY, Name TEXT, Age INTEGER)");
    }

    /**
     * @Transactional
     */

    public function createUser($name, $age)
    {
        $sth = $this->db->prepare("INSERT INTO User (Name, Age) VALUES (:name, :age)");
        $sth->execute(array(':name' => $name, ':age' => $age));
    }

    /**
     * @Template
     */

    public function readUsers()
    {
        $sth = $this->db->query("SELECT name, age FROM User");
        $result = $sth->fetchAll(\PDO::FETCH_ASSOC);
        return $result;
    }
}

モジュール

モジュールではインターフェイスと実クラスやインスタンス、ファクトリを紐づけるコードを記述します。ユーザー定義のアノテーションはインターセプターと紐づけます。インターセプターはネスト可能でこの例では@TransactionalとマークされたメソッドはTimerとTransactionの機能がネストされて適用されます。

class UserModule extends AbstractModule
{
    protected function configure()
    {
        $pdo = new \PDO('sqlite::memory:', null, null);
        $this->bind('PDO')->annotatedWith('pdo_user')->toInstance($pdo);
        $this->registerInterceptAnnotation('Transactional', array(new Timer, new Transaction));
        $this->registerInterceptAnnotation('Template', array(new Template));
    }
}

インターセプター

メソッド実行に割り込み、元メソッドの前後の処理をコーディングします。このタイマーインターセプターでは タイマーのスタートとストップの間に$invocation->proceed();として元のメソッドが実行されています。 array(new Timer, new Transaction)と指定することで、タイマースタート、トランザクションスタート、元メソッド実行、トランザクションコミット、タイマーストップと処理がネストされインターセプターに挟まれたその中心で元メソッドが実行されます。


@IT総合トップ > @IT CORE > Java Solution > Java EE 5マイグレーションプラクティス(1)より


Manjesh’s Blog Aspect Oriented Programming and Unity 2.0 より

タイマーインターセプター

/**
 * Timer interceptor
 */

class Timer implements MethodInterceptor
{
    public function invoke(MethodInvocation $invocation)
    {
        echo "Timer start\n";
        $mtime = microtime(true);
        $invocation->proceed();
        $time = microtime(true) - $mtime;
        echo "Timer stop:[" . sprintf('%01.7f', $time) . "] sec\n\n";
    }
}

トランザクションインターセプター

リフレクションを使い元オブジェクトのプライベートプロパティのPDOオブジェクトを操作してトランザクションを実現しています。

/**
 * Transaction interceptor
 */

class Transaction implements MethodInterceptor
{
    public function invoke(MethodInvocation $invocation)
    {
        $object = $invocation->getThis();
        $ref = new \ReflectionProperty($object, 'db');
        $ref->setAccessible(true);
        $db = $ref->getValue($object);
        $db->beginTransaction();
        try {
            echo "begin Transaction" . json_encode($invocation->getArguments()) . "\n";
            $invocation->proceed();
            $db->commit();
            echo "commit\n";
        } catch (\Exception $e) {
            $db->roleback();
        }
    }
}

テンプレートインターセプター

連想配列をフォーマットされた文字列に変換しています。

/**
* Template interceptor
*/

class Template implements MethodInterceptor
{
    public function invoke(MethodInvocation $invocation)
    {
        $view = '';
        $result = $invocation->proceed();
        foreach ($result as $row) {
            $view .= "Name:{$row['Name']}\tAge:{$row['Age']}\n";
        }
        return $view;
    }
}

インジェクター

インジェクターを生成し、モジュールをセットして対象のインスタンスを取得します。インスタンス取得時にオブジェクトグラフ(必要オブジェクトのリレーション)が解決され依存するオブジェクトが全て生成(またはレイジーロード可能なオブジェクトを取り出す機能のみを持ったオブジェクトプロバイダー)がセットされ対象インスタンスが生成されます。

モジュールは通常のwebアプリケーションならbootstrapで1回だけ作成します。@Aspectとマークされメソッドインターセプトされるオブジェクトは、Weaveオブジェクトというメソッドがインターセプトされ代理実行されるプロキシーオブジェクトに変わります。元のオブジェクトのメソッドを受付け、元のオブジェクトのように振る舞う代理オブジェクトです。

$injector = include 'path/to/scripts/instance.php';
$injector->setModule(new UserModule);
$user = $injector->getInstance('Ray\Di\Sample\User');
/* @var $user \Ray\Di\Sample\User */
$user->createUser('Koriym', rand(18,35));
$user->createUser('Bear', rand(18,35));
$user->createUser('Yoshi', rand(18,35));
$users = $user->readUsers();
var_export($users);

実行結果

Timer start
begin Transaction["Koriym",33]
commit
Timer stop:[0.0001919] sec

Timer start
begin Transaction["Bear",32]
commit
Timer stop:[0.0001190] sec

Timer start
begin Transaction["Yoshi",27]
commit
Timer stop:[0.0001149] sec

Name:Koriym Age:19
Name:Bear Age:28
Name:Yoshi Age:18

オリジナル実行

オリジナルのメソッドをそのまま実行した場合する場合のコードと結果です。ターゲットクラスに依存技術がなく、プレーンな形で実行とテストが可能です。

$pdo = new \PDO('sqlite::memory:', null, null);
$user = new \Ray\Di\Sample\User($pdo);
$user->init();
$user->createUser('Koriym', rand(18,35));
$user->createUser('Bear', rand(18,35));
$user->createUser('Yoshi', rand(18,35));
$users = $user->readUsers();
var_export($users);

array (
  0 =>
  array (
    'Name' => 'Koriym',
    'Age' => '33',
  ),
  1 =>
  array (
    'Name' => 'Bear',
    'Age' => '20',
  ),
  2 =>
  array (
    'Name' => 'Yoshi',
    'Age' => '27',
  ),
)

Conclusion

依存オブジェクトが注入される元のクラスには特定のベースクラスの継承や、DIコンテナ等特定の技術に対する依存がありません。利用するオブジェクトや値は全て外部から入力されます。インターフェイスやアノテーションを、クラスやインスタンスまたはファクトトリークラスと結びつけたモジュールを用いて、インジェクターが必要とするオブジェクトを注入 1 します。

アノテーションでマークされたメソッドはインターセプトされるメソッドと解釈され、代理オブジェクトによってそのメソッドが代理実行されます。各処理を横断的に共有する関心の実行に役立つと同時に、メソッド内の処理を本質的なものと付帯的なもの、それぞれドメインロジック(ビジネスルール)、アプリケーションロジック(認証やロギング)と分離する事にも役立ちます。オブジェクト指向プログラミングの大原則に関心事の分離があるとするとアスペクト指向プログラミングはそれを補完する横断的関心事の分離に他なりません。

この記事ではRayの基本的な使用法の紹介だけにとどめ、DIやAOPの効用や用語、概念の詳しい解説は行いませんでした。またパフォーマンスやこの技術が向いている問題領域、不向きな領域、アプリケーションでの可能性や、現在ある課題にも触れていません。プレビューリリースとして基本機能を簡単にご紹介しました。

DIに関するより良い議論

先日行われたZendConでZend FrameworkチームのエンジニアのRalph Schlenderさんがzf2のZend Diについてスライドを公開しています。zf2のDIだけでなく、特に前半DIについて語られています。素晴らしい内容で、共感する内容も多いです。紹介します。

Try it

ここで紹介したサンプルアプリはこのテストで簡単に実行することができます。ご協力頂ければ大変ありがたいです。現在は簡単な英文マニュアルがあるだけですがkoriym/Aura.Diで公開しています。


  1. 外部から代入 []

Posted by & filed under General / Personal.

2011年10月5日 Steve Jobs氏死去。
謹んでご冥福をお祈りします。

Steve Jobs


16の時にApple IIcを使い始めて以来のApple製品ユーザーです。

Apple IIc Macinthos SE/30 iMac DV Saige


PowerMac G4 Cube iPod first

人々を引きつける彼のプレゼンテーションでも最も特別なのはやはりMacworld 2007 Keynoteでしょう。その冒頭でJobsは「世の中を変えてしまう製品というのがある。一度でも幸運なことだが、Appleは過去に2つやりとげた。1984年のMacintosh, 2001年のiPod …」とそれから初めて発表するiPhoneへの紹介に繋げます。

この2つの製品の登場は今も強い印象を持っています。ただし単なる熱狂ではなくて疑問と期待が入り交じった不思議なものでした。MacintoshはApple IIで成功した要素「カラー、拡張性、TVをモニターにできる、ゲームに向いた機能」の「全て」を持っていませんでした。

iPodが出現したときには128MのメモリのMP3プレーヤーに人気があり、市場に「ハードディスクを首からぶら下げて持ってる曲を全部持ち運びたい」という要求はありませんでした。iPodの日本初お披露目は(自分の記憶によれば)「ラフォーレ原宿」のロビーのところです。ガラスケースに入った発売前のiPodがうやうやしく回転してました。たまに見る人もいましたが、それがコンピューターメーカーの製品、ましてやすぐ数年後に音楽の聞き方を全く変えてしまう革新的な製品であると見てる人は全然いなかったと思います。

MacintoshもiPodも価格が高すぎるとも言われ、どちらも市場に登場した時には少なからず批判もありました。



今は分かります。
真にイノベイティブな製品は同時代の人に登場の時からすぐに熱狂的に支持されるものではないということを。

Keynoteの中でJobsは我々の世界の先駆者としてAllan Kayの言葉を引用します。

ソフトウェアに対して本当に真剣な人は、独自のハードウェアを作るべきだ。

iPhone登場30年前の言葉ですが、今のAppleそのものです。
そのAllan Kayが好んで使った言葉があります。

“Perspective is worth 80 IQ.”

“Knowledge is silver. Outlook is gold. IQ is a lead weight.”

物事を見る視点や見解の大切さを説いた言葉ですが、Steve Jobsは正に世界を人と違った視点で見てた人だと思います。人とは違う考えを持つことを恐れず、世界を変えられるとの信念を持つ大胆さ、そしてそれを実行できる能力を兼ね備えていた、世界はそういう惜しい人を無くしました。自分も今日は大きな喪失感と共に一日を終えました。

最後にJobsが制作に最も拘ったと言われるCM映像1 を紹介してこの記事を終えます。追放されたAppleに復帰したJobsがどん底のAppleの再生を願い制作したそうです。

Steve Jobs本人のナレーションによる「Think Diffrent」です。

クレージーな人達がいる。
反逆者,厄介者と呼ばれる人達。
四角い穴に、丸い杭を打ち込むように
物事をまるで違う目で見る人たち。

彼らは規制を嫌う。彼らは現状を肯定しない。

彼らの言葉に心をうたれる人がいる。
反対する人も、賞賛する人も、けなす人もいる。
しかし、彼らを無視することは、
誰にも出来ない。

なぜなら彼らは物事を変えたからだ。
彼らは人間を前進させた。

彼らはクレージーと言われるが、
私たちは彼らを天才と思う。

自分が世界を変えられること
本気で信じる人達こそが、
本当に世界を変えているのだから。


  1. http://jp.wsj.com/japanrealtime/2011/10/06/%EF%BD%97%EF%BD%93%EF%BD%8A%E6%97%A5%E6%9C%AC%E7%89%88%E7%B7%A8%E9%9B%86%E9%95%B7%E3%81%8C%E8%A6%8B%E3%81%9F%E3%82%B8%E3%83%A7%E3%83%96%E3%82%BA%E6%B0%8F%E3%81%AE%E5%AE%8C%E3%81%BA%E3%81%8D%E4%B8%BB/ []

Posted by & filed under PHP, フレームワーク.

カンファレンスの基調講演かよというような大げさなタイトルですが、参加したPHP勉強会@関東で空き時間があって、持っていったMacBook Airに入っていた以前つくった資料の断片がありその場で飛び入りを決め発表させてもらいました。

元々は自分が5.3+専用のフレームワークをつくるときに、何故作るのか、今求められるものとは何か、現在のweb開発、PHP、フレームワークの潮流とはどういうものなのか、等と自分のフレームワーク制作用に調べた時につくった資料の断片で公開の予定のないものでした。

自分なりに客観性を持って考察したのですが、やはりこういう観察や考察は多分に主観的なところとかあると思います。これと別スライドのCQRSパターンとMozilla laboのCANVASを使ったAceエディターが前のbespineから大分進化してて充分実用レベルです、デザイナーや開発者どうしでのコラボレーションに充分使えます。GitHubで使われてますが、開発で使うと使い勝手いいですよいう話、以上3つの話をいたしました。


Posted by & filed under PHP.

詳細はhttp://php-osx.liip.ch/を参照。以下元記事の間違い 1を訂正したまとめです。

PHPのインストール

/usr/local/php5/にインストールされます。

curl -s -o /tmp/packager.tgz http://php-osx.liip.ch/packager/packager.tgz
sudo tar -C /usr/local  -xzf /tmp/packager.tgz
sudo /usr/local/packager/packager.py install beta-frontenddev

確認

現在PHP 5.4.0beta2-dev というバージョンがインストールされます。2

$ /usr/local/php5/bin/php -v

PHP 5.4.0beta1-dev (cli) (built: Aug 23 2011 11:16:42)
Copyright (c) 1997-2011 The PHP Group
Zend Engine v2.4.0, Copyright (c) 1998-2011 Zend Technologies
    with Xdebug v2.2.0-dev, Copyright (c) 2002-2011, by Derick Rethans

apapcheのhttpd.conf

OSX純正のapache2の場合

/etc/apache2/httpd.conf

LoadModule php5_module        libexec/apache2/libphp5.so

LoadModule php5_module        /usr/local/php5/libphp5.so

に変更

アンインストール

httpd.confを元のものに直してからphp5ディレクトリを消去します。

$ rm -rf /usr/local/php5
$ rm -rf /usr/local/packager/

試してみよう

PHP開発コミュニティへの協力

Rasmus Lerdorさんが“make test”の協力の呼びかけを行っています。ソースをコンパイルしてmake testとすると、test結果が自動的に送信されるような仕組みになっています。簡単に行えます。この記事とは直接の関係はありませんが紹介します。

まとめ

簡単です。インストールに時間もかからず、リンクエラーに悩まされる事も無く多くのextensionもサポートされていて開発用に良いんじゃないでしょうか。php5.3環境をmacports、php5.4環境をosx純正+このバイナリインストールのPHP5.4と使い分けています。


  1. sudo tar -C / -xzf /tmp/packager.tgzになっている。issue報告済み []
  2. 2011/10/24 []

Posted by & filed under PHP, プログラム.

今年もGoogle Developers Days 2011のDevQuizに挑戦しました。
以下、その記録です。

Web Game

Chromeのエクステンションを作成。サンプルに少し追加して全てのカードをクリックするようにして解答しました。

多くの人が思ったようにChromeのエクステンションは簡単にできるのよく分かりました。solver.jsとmanifest.jsonの2つのファイルを作成するだけです。

このエクステンションはhttp://gdd-2011-quiz-japan.appspot.com/webgame/problemのサイトに出現する全てのカードをクリックします。

solver.js

var i = j = 0;
cont = true;
while (cont == true) {
    var element0 = document.getElementById('card' + i);
    var element1 = document.getElementById('card' + j);
    console.log(j);
    if (element0 == null) {
        cont = false;
    }
    if (element1 == null) {
        console.log('Card element is not found. Check element id.');
        i++;
        j = i;
    } else {
        var myevent = document.createEvent('MouseEvents');
        myevent.initEvent('click', false, true);
        element0.dispatchEvent(myevent);
        element1.dispatchEvent(myevent);
        console.log('Card color is "' + element1.style.backgroundColor + '".');
    }
    j++;
}

manifest.json

{
  "name": "ChromeExtensionSolverHint",
  "version": "1.0",
  "description": "Open the first card and show background color of the card.",
  "content_scripts": [
    {
      "matches": [
        "http://gdd-2011-quiz-japan.appspot.com/webgame/problem*"
      ],
      "js": [
        "solver.js"
      ]
    }
  ],
  "permissions": [
  ]
}

Go

Go言語で与えられた画像ファイルの使用色数を答える関数を作成するという問題です。

まずはOSX/LionにGoをインストールです。このサイトを参考にしました。1
http://jeremyhubert.com/articles/installing-google-go-on-osx-snow-leopard.html

Go言語は初めてだったのですが触ってすぐに色々な興味深い特徴に気がつきます。2
静的型付けのコンパイラ言語なのですが、丁寧に削られた簡素な記述のおかげかスクリプト言語のような柔軟でソフトな印象を感じました。

package main

import (
    "fmt"
    "io"
    "strings"
    imagep "image/png"
    /* add more */
)

func CountColor(png io.Reader) int {
    image, err := imagep.Decode(png)
    if err != nil {
        fmt.Printf("Error from png.Decode: %s\n", err)
    }
    width := image.Bounds().Size().X
    height := image.Bounds().Size().Y
    //fmt.Printf("width: %d, %d\n", width, height)
    result := map[uint32]bool{}
    var idx uint32
    for y:=0 ; y < height ; y++{
        for x:= 0 ; x < width ; x++ {
            color := image.At(x, y)
            //fmt.Printf("x, y, color: %d %d %x\n", x, y, color)
            r, g, b, _ := color.RGBA();
            idx = r*0x100*0x100 + g*0x100 + b
            result[idx] = true
        }
    }
    cnt := len(result)
    return cnt
}
/* これらの関数は提出時に自動挿入されます。 */
func main() {
    png := GetPngBinary()
    cnt := CountColor(png)
    fmt.Println(cnt)
}

func GetPngBinary() io.Reader {
    // img_strの中身は提出するたびに変化します。
    img_str := "\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x08\x08\x06\x00\x00\x00\xe3\xa1?c\x00\x00\x02\xeeiCCPICC Profile\x00\x00x\x01\x85T\xcfk\x13A\x14\xfe6n\xa9\xd0\"\x08Zk\x0e\xb2x\x90\"IY\xabhE\xd46\xfd\x11bk\x0c\xdb\x1f\xb6E\x90d3I\xd6n6\xeb\xee&\xb5\xa5\x88\xe4\xe2\xd1*\xdeE\xed\xa1\x07\xff\x80\x1ez\xf0d/J\x85ZE(\xde\xab(b\xa1\x17-\xf1\xcdnL\xb6\xa5\xea\xc0\xce~\xf3\xde7\xef}ov\xdf\x00\rr\xd24\xf5\x80\x04\xe4\r\xc7R\xa2\x11il|Bj\xfc\x88\x00\x8e\xa2\tA4%U\xdb\xecN$\x06A\x83s\xf9{\xe7\xd8z\x0f\x81[V\xc3{\xfbw\xb2w\xad\x9a\xd2\xb6\x9a\x07\x84\xfd@\xe0G\x9a\xd9*\xb0\xef\x17q\nY\x12\x02\x88<\xdf\xa1)\xc7t\x08\xdf\xe3\xd8\xf2\xec\x8f9Nyx\xc1\xb5\x0f+=\xc4Y\"|@5-\xce\x7fM\xb8S\xcd%\xd3@\x83H8\x94\xf5qR>\x9c\xd7\x8b\x94\xd7\x1d\x07inf\xc6\xc8\x10\xbdO\x90\xa6\xbb\xcc\xee\xabb\xa1\x9cN\xf6\x0e\x90\xbd\x9d\xf4~N\xb3\xde>\xc2!\xc2\x0b\x19\xad?F\xb8\x8d\x9e\xf5\x8c\xd5?\xe2a\xe1\xa4\xe6\xc4\x86=\x1c\x185\xf4\xf8`\x15\xb7\x1a\xa9\xf85\xc2\x14_\x10M'\xa2Tq\xd9.\r\xf1\x98\xae\xfdV\xf2J\x82p\x908\xcada\x80sZHO\xd7Ln\xf8\xba\x87\x05}&\xd7\x13\xaf\xe2wVQ\xe1y\x8f\x13g\xde\xd4\xdd\xefE\xda\x02\xaf0\x0e\x1d\x0c\x1a\x0c\x9a\rHP\x10E\x04a\x98\xb0P@\x86< \x1a14\xb2r?#\xab\x06\x1b\x93{2u$j\xbbtbD\xb1A{6\xdc=\xb7Q\xa4\xdd<\xfe(\"q\x94C\xb5\x08\x92\xfcA\xfe*\xaf\xc9O\xe5y\xf9\xcb\\\xb0\xd8V\xf7\x94\xad\x9b\x9a\xba\xf2\xe0;\xc5\xe5\x99\xb9\x1a\x1e\xd7\xd3\xc8\xe3sM^|\x95\xd4v\x93WG\x96\xacyz\xbc\x9a\xec\x1a?\xecW\x971\xe6\x825\x8f\xc4s\xb0\xfb\xf1-_\x95\xcc\x97)\x8c\x14\xc5\xe3U\xf3\xeaK\x84uZ17\xdf\x9fl\x7f;=\xe2.\xcf.\xb5\xd6s\xad\x89\x8b7V\x9b\x97g\xfdjH\xfb\xee\xaa\xbc\x93\xe6U\xf9O^\xf5\xf1\xfcg\xcd\xc4c\xe2)1&v\x8a\xe7!\x89\x97\xc5.\xf1\x92\xd8K\xab\x0b\xe2`m\xc7\x08\x9d\x95\x86)\xd2m\x91\xfa$\xd5``\x9a\xbc\xf5/]?[x\xbdF\x7f\x0c\xf5Q\x94\x19\xcc\xd2T\x89\xf7\x7f\xc2*d4\x9d\xb9\x0eo\xfa\x8f\xdb\xc7\xfc\x17\xe4\xf7\x8a\xe7\x9f(\x02/l\xe0\xc8\x99\xbamSq\xef\x10\xa1e\xa5ns\xae\x02\x17\xbf\xd1}\xf0\xb6nk\xa3~8\xfc\x04X<\xab\x16\xadR5\x9f \xbc\x01\x1cv\x87z\x1e\xe8)\x98\xd3\x96\x96\xcd9R\x87,\x9f\x93\xba\xe9\xcabR\xccP\xdbCRR\xd7%\xd7eK\x16\xb3\x99Ub\xe9v\xd8\x99\xd3\x1dn\x1c\xa19B\xf7\xc4\xa7Je\x93\xfa\xaf\xf1\x11\xb0\xfd\xb0R\xf9\xf9\xacR\xd9~N\x1a\xd6\x81\x97\xfao\xc0\xbc\xfdE\xc0x\x8b\x89\x00\x00\x00\x97IDAT(\x15\x9d\x92\x81\x0e\x80 \x08D\xa5\xf9m\xf5Y\xad\xcf\xaa\x9f#nz$\xba\xb9\xca\xad\x85\xc1\xbd\x03S\xd4\x96H\xf2\xa5\xea\xe1\xeb@\x8e\x02\xd0}\x14/\x80\x03\xca\xa75{\xeb0\x80\x01\xa9\xa0\xdcC|\x02:\xf1\xc3U\xc7\\K\x97}\xda9HPcq0pQ\x8aE\xe94y\x05'3\x92M[\x86\xc7\xc1\xa4n\x82\x01\x8ci\xe2\xc5\x7f\x02N`\xda\xdcCK\xaeqbqsD\xbd\x86=\xe0g\xdb\x9dy\xba\xb4Xp\x8bX\xf0\xe7\x8d\x89g\x84pD_\x0cx\x9438x7\xa4yC\r7\x04z\xae\x00\x00\x00\x00IEND\xaeB`\x82"
    return strings.NewReader(img_str)
}

ポイントというほどの所もないですが、色を数えるのに各色8bitなので16進数3桁の数字にしてキーにしその数を数えました。

スライドパズル

大きさが色々あり、盤面の中にも壁がある15パズルの変形版のようなパズルを5000問解くという問題です。一問0.1点。これまでの3つのクイズの合計100点あったのですが、101点になるために今までの100点獲得の労力を軽く超えるような難易度でした。難しかったです。

CPU100%でファンを全開で一晩中計算し続けさせるようなパズルの解法のプログラムは、ms単位で処理の終わる普段のwebプログラムと全く違う世界です。AIプログラミングの基礎的な事から分かってなかったのでその学習から始めました。前回のGDD Pacman問題の反省です。3 

まず大事なのは問題をツリー構造にモデル化することです。問題プロセスの状態を節点(node)、その状態遷移を枝(branch)で表します。一番初めの節点を根(root)、一番末端を葉とした状の不可逆のツリー構造をモデルとし、そのルートからゴールまでの状態遷移に様々な探索方法をあてはめ解法を求めます。

競技用プログラムやAIプログラムに慣れてる方ならこの辺りの話は基礎の基礎なのだと思いますが、自分は一つ一つをあーなるほどと理解するところから始めました。

最良優先探索はいくつもあるのですが、向いてそうなのは A*、評価関数はオーソドックスなマンハッタン距離に少しオリジナルな関数を加えたらと考えました。

言語の選択

最初は普通に考えて速度の速いコンパイル言語をと、ちょっと学習してみたGoか普段使わないC/C++/Javaに挑戦するかとか考えました。4 しかし#gdd11jpでPythonで高得点を取ってる方がいたのを見たのと 5 使った事のないPHP5.3の機能ややってみたい実装があったのでPHPにしました。6。目標として(使い切りだし時間も限られてますが)丁寧なOOPでマンハッタン距離+αのヒューストリックのA*で実装というのを立てます。

実装

https://github.com/koriym/gdd11jp

以下、READMEからです。

■slide.php
本体。answer.txtがあれば解答済みファイルとしてスキップに利用、答えはoutput.txtで出力。
どのパズルをスキップするか、およびパズルに応じたタスク管理を選択するロジックをクロージャで渡して演算を開始。

幅優先探索は、反復深化深さ優先探索ははどちらもブラインドサーチで3×3と3×4,4/3のみに対応。それ以上は=(壁)検知付きマンハッタン距離、正位置にあるピースに重みづけ、それに手数削減のための過去の移動距離をそれぞれコストとして合計したものを関数としてヒューリスティック探索。繰り返し検知、逆探索検索付き。消費メモリ、消費時間に応じて無回答。制限時間になってもヒューリスティック関数の値が上昇し続けてたら時間制限を延長する”もう少しかも、頑張れ”機能。”下端を除くゲーム盤面の端で1つ足りないだけの状態にペナルティの効果”は今ひとつ。

演算コスト削減は、マンハッタン距離コストマップ、移動可否マップの事前演算。メモリ省力化として配列のSplFixedArray使用等。基本的な探索実装で目標スコア3000後半程度。肝心のソルバーは強くないが想定の範囲は大体実装。

クラス

PuzzuleRepository
ファイルからパズルオブジェクトを取り出せるリポジトリクラス。
Puzzle
パズルの基本データ。プロパティの公開されているデータ構造クラス。
Game
ゲーム状態を保持しパズルの基本操作ができるオブジェクト。方向と合わせてタスクとして登録される。
Play
ルールを知りプレイを行うクラス。ゲーム完了するまでループ実行。
Task
BFS, IDDFS, A*探索を行うためのタスクマネージメントクラス。
それぞれPHPのSplQueue、SplStack、SplPriorityQueueクラスを利用。

PHPには A*にそのまま適用できるSplPriorityQueue クラス、幅優先探索にSplQueueクラス、深さ優先探索 にSplStack クラスという標準的なデータ構造用のクラスが5.3から用意されていてこれを利用しました。
肝心のソルバーはマンハッタン距離(途中の壁検知付の)と移動ヒストリーの長さを合算してコストにするものと、その逆探索の基本的なものですが、パズルを二次元配列にしないで高速化と処理の簡素化のために一次元にしたのと、移動処理の時のif R, if L …と4つ続ける冗長さを排除したのが小さなこだわりです。双方向探索の実装をしてれば大きくスコアを伸ばせたのではないかと思います。高速な言語に移植する方法もありましたがPHPと基本的なアルゴリズムでどれくらいのスコアが取れるのだろうという興味もありました。少しの改善で4000ありえるかなあというところでタイムアップでした。3750/5000です。高得点とは言えませんが、幅優先実装してたった0.57点でPHPじゃやっぱり…と途方にくれた時から考えると大きな前進でした。

Clean Code

限られた時間である程度の結果を出しながら多くのOOP原則に従ったClean Codeで記述というのも今回の目標の一つでした7
単一責任原則(SRP:the Single Responsibility Principle) 、デメテルの法則(Law of Demetre (LoD))、おまえが呼ぶな、俺が呼ぶの「ハリウッドの原則」、オープン・クローズドの原則(OCP)、gettr/setterの排除、等々意識したのですが不十分な所も多々あり反省の残るところです。それでも”次”にはかなり再利用できそうです。また、PHPのコーディングでprivate/protectedのprefixに_(アンダースコア)を使わないスタイルがいいのでは?にというトレンドが一部であるようで今回はそれに従ってみました。その他は現在のPHPのごく標準的なコード規約に従ってると思います。

実行方法

$ git clone git@github.com:koriym/gdd11jp.git
$ cd gdd11jp/puzzle
$ php slide.php

まとめ

コンテストと違ったイベント感、使用言語を超え同じ取り組みをする事で生まれる共有感、高得点の人もそうでない人も、DevQuizはプログラミングが本来持つコーディングの楽しみが実感できる素晴らしい機会だと思います。8 機会を与えてくれた主催者の方々と#gdd11jpで楽しい時間を共有してくれた皆様には感謝を申し上げます。ありがとうございました。GDD11でお会いしましょう!


  1. コンパイルとリンクを行うスクリプトもあって便利です。 []
  2. 例えば、変数名の後に型が続く事や、大文字始まりならpublic宣言など。また未使用変数があればコンパイルにすら通りません。 []
  3. 自分で適当に考え実装し後からああ自分の実装はBFSというやつだったのか..などとならないように! []
  4. Cはその昔SonyのPlayStation用ゲーム制作の一部を手伝った事があるくらいでポリゴンで3Dの関節プログラムとか、アセンブラのゲームの移植とか、すっかり忘れてるのですが再挑戦も面白いと思いました []
  5. Cythonというコンパイラがあって超高速で実行できるのは後で知りました^^; []
  6. どうせならPHP5.4でやれば良かったですね []
  7. 点にはなりませんが []
  8. 中学生の時にはじめてプログラミングしたワクワク感が少し甦りました。 []

Posted by & filed under PHP, フレームワーク.

Hello Worldベンチマーク

去年のHello Worldコールグラフに続いて再度HelloWorldグラフをとりました。

グラフをとるためにリファレンスにしたHelloWorldベンチマークのサイトは前回とは違い、SolarPHPというフレームワークのリードDeveloperのPaul M Jones氏のphp-framework-benchmarksというプロジェクトです。

SolarPHP自体はYiiのようにミニマムオーバーヘッドを特別アピールしているフレームワークではありませんが、Paul M Jones氏は数年前から定期的にこのような発表を行っていて、PHPでこの種のベンチマークに最も熱心に取り組んでる人だと思います。 Symfony2.0のリードコミッターのFabien氏もこのプロジェクトをforkしてSymfony2.0を加えています。

HelloWorldアプリのスペック

基準となるHelloWorldアプリは以下のようなものです。

  • 最小限のアプリ(フルアプリケーションでない)
  • 通常のconfig
  • アクションコードなし
  • ビューレイアウトもビューヘルパーもなし
  • スタティックなテキスト
  • ページキャッシュなし
  • データーベースなし

アプリケーションロジックを含まないが「HelloWorldに徹底的にカスタマイズしたされたものでもない」、ごく標準的仕様のミニマムアプリです。

このアプリのベンチマークにはどういう意味があるのでしょうか。また比較にはどういう点に注意が必要でしょうか。スライドから抜粋して紹介します。

ベンチマーク結果の比較

同じフレームワークでの比較

継続的ベンチマーキング、新しいアーキテクチャの採用には実行コストが伴う、その利点/コスト分析として。新旧バージョンでの比較統計。

違うフレームワークでの比較

他システムとの応答比較、アーキテクチャコスト比較等。速度比較をする場合は同じような”スタイル/クラス”(PHP5/デザインパターン/front,pageコントローラ/ビューの分離など)で比べるべきです。つまりCIとKohana、Cake/Lithium/Solar/Symfony/Yii/Zendで比較するのが妥当です。

以上は特にフレームワーク開発者にとって重要です。同じクラス/スタイルのアーキテクチャで同程度の機能で極端な速度さがあればそれは設計や実装に見直す点が有るという事に繋がるでしょう。

Webサイトは高度になり表現方法も多様化しています。1つのページを表示したあと、様々な”ページの断片”が用意されるようなGmailのようなページではそのつぶつぶのような断片的JSONの発行にそれぞれこのミニマムオーバーヘッドコストがかかってます。実行コストを最小化しつつ豊富な機能を実装するような技術的試みが続けられてると思います。12

結果を過大評価しない

スライドの中では「最も遅いフレームワークでも充分早い」と言っています。フレームワークの選定基準には様々な判断材料がありますが、この種のベンチ結果は重要でないことは前回記事のリファレンスのphp-markでも言っていますし、このプロジェクトでも言っています。Do not interpret the numbers alone http://code.google.com/p/phpmark/。この数字を過大評価してフレームワークを選定したり批評したりするのは適切ではないでしょう。

「フレームワーク対決」の結果などではなく 、開発や対策など特定の目的を持ったときに意味のある数字だと思いますがどうでしょうか。

ベンチマーク結果

スライドで紹介されてる結果です。

WARNING: ベンチマークはミスリードさせる可能性があります

フレームワークのスケール

アーキテクチャの似たフレームワーク同士では結果が似たような速度の傾向があります。
DBアクセスを含めたアプリで比較してるcakephperさんの記事と合わせて見ると一層興味深いのではないでしょうか。

ベンチマーク結果の利用

基本的にアプリケーションロジックが取り除かれたこのコードが性能の上限です。キャンペーンや有名サイトでの紹介など一時的高負荷が予想されるときの負荷対策の基準になりえます。3

XHGUI

コールグラフはXHGUIというツールを利用しました。xhprofをフォークしたプロファイリングツールで結果保存にDBを利用し、複数回のプロファイリングを管理できます。

apacheを毎回リスタートさせ、以下のコマンドラインで出た結果のうち良いものをグラフ描画用に使用しています。

ab -c 10 -t 10 http://examplle.com/helloWorld/

XHGUI - ヒストリー画面

XHGUI - プロファイル詳細画面

Hello Worldコールグラフ

本題のコールグラフです。コール数の少ない順に表示しています。
※クリックすると別画面で開きますが画像はとても大きいのに注意してください。

アーキテクチャの似通ったフレームワークはやはり似通ったグラフの大きさとcall数になってますが、Yiiだけ特別です。特筆すべきはCakePHPの前回調査(v1.2)と今回(v1.3)の違いでコール数が1/10以下になっています。4

Yii1.1.5 / 269 calls / 4853x6728 px

Kohaa3.0.9 / 365 calls / 3240x3451 px

CodeIgniter 2.0 / 447 calls / 3515x3250px

Lithium 0.9.9 / 732 calls / 5839x6856 px

SolarPHP 1.1.1 / 1,174 calls / 3228x5477 px

CakePHP 1.3.10 / 1,177 calls / 4767x5982 px

Symfony2 pr4 / 1,274 calls / 4595x7373 px

symfony1.4.8 / Calls 1,661 / 5837x5280 px

Zend Framework 1.11.9 / 1,799 calls / 7324x6819 px

  • FG 35%
  • FG 100%
  • CodeIgniter 2.0 / 447 calls / 3515x3250px
  • XHGUI - プロファイル詳細画面
  • XHGUI - ヒストリー画面
  • フレームワークのスケール
  • hello_world_benchmark
  • Symfony2 pr4 / 1,274 calls
  • Zend Framework 1.11.9 / 1,799 calls
  • Kohaa3.0.9 / 365 calls
  • SolarPHP 1.1.1 / 1,174 calls
  • Lithium 0.9.9 /  732 calls
  • Yii1.1.5 / 269 calls
  • symfony1.4.8 / Calls 1,661
  • CakePHP 1.3.10 / 1,177 calls

  1. そういう意味でSymfony2のベンチ結果に注目していました []
  2. 前回の記事でのCakePHPやsymfonyはこの点にまったく配慮がなかったのですが、今回大きく改良しています。 []
  3. 高負荷のトップサイトなどでデータソースをRDBから他のキャッシュ向けストレージで利用する場合は、データベースの速度はあまり問題にならないでしょう。 []
  4. 前回が大すぎですね []

Posted by & filed under BEAR.

BEAR Sundayデザインメモ

PHP5.3+(またはPHP5.4)専用BEARのデザインメモをスライドとgist(テキストメモ)にしました。

アーキテクチャ的には「リソース指向」の更なる追求と「RESTful CQRS」が大きなテーマです。沢山のアイデアがあるのですが、今回はその2つを前回のBEAR1 to Saturday (2003, 2011)に続いて記事にします。

リソース指向フレームワーク

リソース指向で、MVCのモデルにあたる部分をリソースとして扱うだけでなく、ページやビューにあたる他のコンポーネントもリソースとして扱います。それによりコントローラー、ビューもリソース同様のテスト可能性や再利用性の向上が求められるのではないかと考えます。もし全てのコンポーネントがCLIで簡単にリクエストして結果が求められるのであれば、テストの記述も簡単なものになるのではないでしょうか。キャッシュや外部アプリ/ホストの利用も同様に有効になるのではと思います。1

CQRS (コマンドクエリ責務分離)


CQRSとは「コマンドクエリ責務分離」といって簡単にいうと参照と更新では責任や仕組みを分離しましょうというアーキテクチャパターンです。

Greg Young流CQRS – Mark Nijhof

例えばブログ記事の投稿を考えてみます。記事の投稿の時には、モデルはデータベースのテーブルの様々な項目に興味を持ちます。正規化され、結合され、複数のテーブルが更新されます。テーブルのコラムの情報はアトミック性をもち、関係性を持ちます。リレーショナルデータベースが有効に機能します。

ところが投稿されたこのブログ記事を読み込むときには、記事表示ページはこの関連付けされたリレーションを持った情報に興味を持たない事がほとんどです。結合、ソートされ、HTML化されたブログの記事の「文字列」が読めさえすれば読めればいいのです。この場合はただの1つの文字列がブログ記事IDから読み込めればいいということになります。つまり更新と参照では関心が異なるのです。

これを現状のBEAR(あるいは多くのフレームワーク)では通常はキャッシュとして実装し速度向上を目的にします。キャッシュは生成は簡単ですが破壊のタイミングが問題になることがあります。適当な秒数で破壊再生成したり、データ更新の時にモデルやコントローラが破壊します。

代わりにフレームワークがこれを行い、キャッシュの生成と破壊を透過にするだけでなく、キャッシュ生成のトリガーをユーザーでなくデータ作成、更新時にします。キャッシュというより、読み込み用データを書き込み時に作成すると言った方がピンと来るかもしれません。2

あるいは更新処理が、参照を行うまで保持されているモデルはどうでしょうか?。そのような”遅延更新モデル”ではブログを誰も見なければ、記事が実データベーステーブルにinsertされる事はありません。代わりに更新イベントハンドラ(BEARではリソースリクエストハンドラ)は誰かがそのブログを見てくれるその日まで、その書き込みイベントを保持してるはずです。3

REST meets CQRS

同じインターフェイスを持つ(RESTful)、実処理とそのリクエストが関心に応じて分離(CQRS)がされるという事はフレームワークやアプリケーションにとって他にどういう力をもたらすでしょうか?クエリー(参照)とコマンド(更新)はその実処理においてだけでなく、コーディングについても非対称性があります。多くの場合、更新参照のページの方がコーディングが簡単なのです。

あるモデルの更新と参照を違うプログラマが担う事ができます。参照データだけ用意すれば参照ページが機能するという事は、参照しか必要のないプロトタイプ開発にも役立てる事ができるでしょう。4

リクエストの公開宣言と境界の明確化、リクエストと実行の分離などCQRSの特徴の多くはフレームワークやアプリケーションのみならず、プロジェクトの進め方までに柔軟性を与える優れた可能性を持っているのではと考えます。

webアプリケーションをwebのアーキテクチャで構築する

CQRSのwebフレームワークというものは自分が知る限り数は少なく、またこれをRESTfulにというのはアイデアとしては語られても5 実装を知りません。

しかしCQRSは「コンポーネントよりむしろ接続に注目する」BEARを拡張するアーキテクチャとしては最良のものではないかと考えます。またこの実装はチャレンジな部分が大きく6 開発時に再検証や方針の再検討など必要な不確定部分が多いとは思います。スライドやGistではアイデアを大きく広げましたが、どこもまで、あるいはどのように実装するかは検討すべき点はまだまだあります。メモでかいたデザインのいくつかは実装、検討過程でボツになるかもしれません。7

色々な技術がありますが、やはりこれまでのBEARの開発と運用で学んだ「webアプリケーションをwebのアーキテクチャ8 で構築する」有用性、そこを忘れず軸にして進めたいと思います。

「クライアントやサーバーサイドがどんなに複雑に高度になってもHTTPという接続技術のアーキテクチャが基本的に変わらず機能し続けてきた」 – これにはまだまだ学べる事があると思っています。


  1. 例えばidを受け取ってそのIDのユーザーリソースとリンクされたその友達リソースを取得しHTML viiewリソースにリンクする「ページリソース」を考えてみます。ページはユーザーや友達リソースの値や実装には関心を持ちません。リソースの値の取得・セットではなく、接続性だけが記述してあります。そのように高度に疎結合なページではアプリケーションを超えた利用が可能です。写真サイトでもブログサイトでも「ユーザーと友達を表示する」というリソースの接続性が変わらないためです。写真サイトのユーザーページリソースをブログサイトのユーザーページが利用する事ができます。 []
  2. クローラーがインデックスをつくるように []
  3. つまり、遅延読み込みだけでなく、遅延作成、遅延更新といった遅延結合が可能になります。 []
  4. “この種のアーキテクチャを採用することで得られるメリットとしてもう1つ強調しておきたいものがあります。それは異なるチーム間で作業負荷を分担するのがきわめて簡単であるということです。これはチーム間に時差がある場合に特に言うことができます。ドメインロジックは正しくなければならないものです。これは単価の高い開発者を投入したいと思う場所でしょう。つまり、業務を理解し、正しいコーディングプラクティスを理解した開発者をということです。言っていることは分かりますよね?しかし、参照部分はそれほど重要ではありません。もちろん正しい必要はあるのですが、価値がある場所ではなく、素早く作って1、2年のうちに作り替えることができます。つまり、単価の低い開発者に作ってもらうことができるものだということです。ドメインに関する知識が多く求められることもなく、本当に重要なのは、GUIがどのように機能し、どのようなコマンドが使え、どのようなイベントが要求されるかということだけです。” – Greg Young流CQRS – Mark Nijhofより引用 []

  5. DDD/CQRS REST and CQRS
    []
  6. 今までもそうでしたが []
  7. その過程でよりよい実装アイデアがでるかもしれませんが []
  8. RESTful []