PHP でアノテーション

PHP フレームワーク構想 - devworksのエントリで

Validation は PHP コメントアノテーションを読み込んで自動実行

PHP フレームワーク構想 - devworks

と、書いたのでちょっとアノテーションについて調べてみました。まずは Class/Method/Field のDoccomment を取得してみます。

<?php
/* 
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 * Description of index
 * @author devworks
 * @clazz_ano1
 * @clazz_ano2(hoge)
 * @clazza_ano3(foo, bar)
 */
class AnnotationTest {
    /**
     * @field_ano1
     * @field_ano2(hoge)
     * @field_ano3(foo, bar)
     * @var <type> 
     */
    private $ano;
    /**
     * @method_ano1
     * @method_ano2(hoge)
     * @method_ano3(foo, bar)
     */
    public function anoTest() {

    }
}
$clazz = new ReflectionClass("AnnotationTest");
echo $clazz -> getDocComment();
echo "<br />";
$method = $clazz -> getMethod("anoTest");
echo $method -> getDocComment();
echo "<br />";
$field = $clazz -> getProperty("ano");
echo $field -> getDocComment();
?>

結果はこんな感じ。

/** * Description of index * @author devworks * @clazz_ano1 * @clazz_ano2(hoge) * @clazza_ano3(foo, bar) */
/** * @method_ano1 * @method_ano2(hoge) * @method_ano3(foo, bar) */
/** * @field_ano1 * @field_ano2(hoge) * @field_ano3(foo, bar) * @var */

Doccomment が全て取得できてますね。では「@」の部分だけ取得してみましょう。

<?php
// AnnotationTest は省略

class AnnotationReader {
    /**
     * ReflectionClass
     * @var ReflectionClass
     */
    public $clazz;
    public function __construct($name) {
        $this->clazz = new ReflectionClass($name);
    }
    public function getClassAnnotation() {
        $comment = $this->getDocComment($this->clazz->getDocComment());
        $this->getAnnotation($comment);
    }

    public function getMethodAnnotation($name) {
        $method = $clazz->getMethod($name);
        $comment = $this->getDocComment($method->getDocComment());
        $this->getAnnotation($comment);
    }

    public function getFieldAnnotation($name) {
        $field = $clazz->getProperty($name);
        $comment = $this->getDocComment($field->getDocComment());
        $this->getAnnotation($comment);
    }

    private function getDocComment($docComment) {
        return preg_split("/[\n\r]/", $docComment, -1, PREG_SPLIT_NO_EMPTY);
    }

    private function getAnnotation($comments) {
        foreach ($comments as $line) {
            $line = $this->_removeCommentSlashAster($line);
            if (preg_match("/^@\w+$/",$line) or
                preg_match("/^@\w+\s*\(/",$line)) {
                echo $line."<br />";
            }
        }
    }
    private function _removeCommentSlashAster($line) {
        $line = trim($line);
        $line = preg_replace("/^\/\*\*/","",$line);
        $line = preg_replace("/\*\/$/","",$line);
        $line = preg_replace("/^\*/","",$line);
        return trim($line);
    }
}

で、以下みたいに実行すると

$ano = new AnnotationReader("AnnotationTest");
$ano->getClassAnnotation();
@clazz_ano1
@clazz_ano2(hoge)
@clazza_ano3(foo, bar)

とれましたね。S2PHP5 のソースをぱくったのでここまでは OK。あとは取得できたアノテーションと同名のクラスをインスタンス化しなければなりません。S2PHP5 の場合はコンテナから取得しているのですが、僕のフレームワークでは DI の予定はないので __autoload を実装して annotation ディレクトリにいるアノテーションと同名のファイル名を探して includeしようと思います。
というわけで残りは次回。