ログ日記

作業ログと日記とメモ

タイプヒンティングからオブジェクトの配列をインジェクションするためにネームスペースを使って動的にクラスを生成する

オートロード関数を登録する。
動的にネームスペースを生成してクラスを定義してオブジェクトを生成する。

<?php

namespace laiz\lib\aggregate;

use \laiz\autoloader\Register;
use \laiz\util\Inflector;

class Autoloader implements Register
{
    public function autoload($name)
    {
        $pattern = preg_quote(__NAMESPACE__, '/');
        if (!preg_match("/^$pattern/", $name))
            return;

        $pattern = preg_quote('\\');
        if (!preg_match("/(${pattern}[^$pattern]+)$/", $name, $matches))
            return;

        $className = $matches[1];

        $fullNamespace = str_replace($className, '', $name);
        $realNamespace = str_replace(__NAMESPACE__ , '', $fullNamespace);

        $interface = $realNamespace . Inflector::singularize($className);
        $interface = ltrim($interface, '\\');

        $className = ltrim($className, '\\');

        // fullNamespace: laiz\lib\aggregate\path\to
        // realNamespace: \path\to
        // interface    : path\to\Object
        // className:     Objects

        // debug
        //var_dump($fullNamespace, $realNamespace, $interface, $className);

        eval("
namespace $fullNamespace;
use \\ArrayObject;
use \\laiz\\builder\\Container;
class $className extends ArrayObject
{
    public function __construct(\$input = null, \$flag = 0, \$iterator = 'ArrayIterator')
    {
        if (\$input === null){
            \$input = Container::getInstance()->getComponents('$interface');
        }
        parent::__construct(\$input, \$flag, \$iterator);
    }
}
");
    }
}


Registerはオートロード関数を登録するためのインターフェースで、getComponents(interface)はインターフェースを実装しているクラスを全て読み込んで生成して配列で返すメソッド。


laiz\lib\aggregate; 以下のネームスペースを利用しようとすると、このオートロードが動く。

<?php
use \laiz\lib\aggregate\anyfeature\Filters;

$filters = new Filters();

とすると、anyfeature\Filter を実装したオブジェクトの配列(ArrayObject)を取得できる。



なんでこんなまどろっこしいことをやってるのかというと…

<?php
use \laiz\lib\aggregate\anyfeature\Filters;

class MyAction
{
    public function act(MyObject $obj, Filters $filters)
    {
        foreach ($filters as $filter){
            $filter->filter($obj)
        }
    }
}

これでタイプヒンティングからオブジェクトの配列をインジェクションできるようになった。
上のMyObjectは今までも自動生成していたが、配列の場合はタイプヒンティングがArrayになるから判別不能だった。
namespaceを使う前はやっつけで Array $Anyfeature_Filters などと書いて変数名から判別していたが、バックスラッシュが絡むとどうにもこうにも。


これ書きながら、わざわざnamespaceを隔離して新しい場所に作らない方法もあるなと思いつつ…とりあえずこれでしばらく使ってみる。




namespaceとかクロージャとかで検索したら、PHP5.3の発表前後のマニュアル的な記事が上位に上がってくる。
まだ普通に使われるところまでいってないのかな。