4

少し前に書いたフレームワークを更新中です。名前空間や自動読み込み機能の使用など、新しい標準を採用したいと思います。

現在、私のフレームワークには、次のような非常に基本的ですが機能的な自動ロード機能があります。

protected function runClasses()
{
    $itemHandler = opendir( V_APP_PATH );
    while( ( $item = readdir( $itemHandler ) ) !== false )
    {
        if ( substr( $item, 0, 1 ) != "." )
        {
            if( !is_dir( $item ) && substr( $item, -10 ) == ".class.php" )
            {
                if( !class_exists( $this->registry->$item ) )
                {
                    require_once( V_APP_PATH . $item );
                    $item  = str_replace( ".class.php", "", $item );
                    $this->registry->$item = new $item( $this->registry );
                }
            }
        }
    }   
}

コードでわかるように、この関数は単一のフォルダーのみに制限されていますが、その利点は、クラスをレジストリにロードして、他のファイルの特定のクラスに、私と同様のことを行うことでアクセスできることです$this->registry->Class->somevar。必要。

私が達成する必要がある/望んでいるのは、オートローダー機能を使用することですが、その機能は単一のフォルダーに限定されず、代わりに複数のフォルダーをナビゲートして必要なクラスをインスタンス化できます。

いくつかのテストファイルを実行しています。現在のファイル構造は次のとおりです。

ファイル構造

MyClass2の場合:

namespace model;
Class MyClass2 {
    function __construct() {
        echo "MyClass2 is now loaded!";
    }
}

MyClass1の場合:

Class MyClass1 {
function __construct() {
         echo "MyClass1 is now loaded!<br />";
    }
}

そしてオートロードのために私は持っています:

function __autoload( $className ) {
    $file = $className . ".php";
    printf( "%s <br />", $file );
    if(file_exists($file)) {
        require_once $file;
    }
}

$obj = new MyClass1();
$obj2 = new model\MyClass2(); 

私の質問は、MyClass2のファイルが見つからないように設定されているため、何を間違えたのか疑問に思っています。次に、最初の「autoload」関数のように、名前空間を指定する必要がない方法はありますか。自動ロードファイルに入れて、レジストリに割り当てますか?

このような長い質問で申し訳ありませんが、どんな助けでも大歓迎です。

4

2 に答える 2

7

ここで 2 つのことがわかります。

最初のものはあなたの問題を少し複雑にします。名前空間を利用したいのですが、現在の構成はファイル システム経由です。これまでのところ、クラス定義ファイルのファイル名には名前空間が含まれていません。そのため、実際に行っていることをそのまま続けることはできません。

2 つ目は、PHP のオートローディングの対象となるものがないことです。定義済みの一連のクラスをロードしてレジストリに登録するだけです。

ここで PHP の自動読み込みが必要かどうかはよくわかりません。確かに、両方を一緒にすることはあなたにとって有望に見えるかもしれません. 最初のポイントを解決すると、おそらく後の問題を解決するのに役立つので、最初にそれから始めることをお勧めします.

隠された依存関係をより見やすくしましょう。現在の設計では、次の 3 つが得られます。

  1. オブジェクトがレジストリに登録される名前。
  2. クラス定義を含むファイル名。
  3. クラス自体の名前。

2. と 3. の値は 1 つです。ファイル名からクラス自体の名前を解析します。書かれているように、名前空間はこれを複雑にします。解決策は簡単です。ディレクトリ リストから読み取る代わりに、この情報を含むファイルから読み取ることができます。軽量の構成ファイル形式は json です。

{
    "Service": {
        "file":  "test.class.php",
        "class": "Library\\Of\\Something\\ConcreteService"
    }
}

これには、ファイル名もわかっているため、クラスを名前でレジストリに登録するために必要な 3 つの依存関係が含まれています。

次に、クラスをレジストリに登録できるようにします。

class Registry
{
    public function registerClass($name, $class) {
        $this->$name = new $class($this);
    }
}

そして、json 形式のローダー クラスを追加します。

interface Register
{
    public function register(Registry $registry);
}

class JsonClassmapLoader implements Register
{
    private $file;

    public function __construct($file) {

        $this->file = $file;
    }

    public function register(Registry $registry) {

        $definitions = $this->loadDefinitionsFromFile();

        foreach ($definitions as $name => $definition) {
            $class = $definition->class;
            $path  = dirname($this->file) . '/' . $definition->file;

            $this->define($class, $path);

            $registry->registerClass($name, $class);
        }
    }

    protected function define($class, $path) {

        if (!class_exists($class)) {
            require($path);
        }
    }

    protected function loadDefinitionsFromFile() {

        $json = file_get_contents($this->file);
        return json_decode($json);
    }
}

ここにはあまり魔法はありません.jsonファイルのファイル名は、そのディレクトリに対して相対的です。クラスがまだ定義されていない場合 (ここでは PHP オートローディングをトリガーしています)、クラスのファイルが必要です。それが完了すると、クラスはその名前で登録されます。

$registry = new Registry();
$json     = new JsonClassmapLoader('path/registry.json');
$json->register($registry);

echo $registry->Service->invoke();  # Done.

この例も非常に簡単で、機能します。これで最初の問題は解決です。

2 つ目の問題は、オートローディングです。この現在の亜種とあなたの以前のシステムは、別の何かを隠していました。やるべきことは2つあります。1 つは実際にクラス定義をロードすることで、もう 1 つはオブジェクトをインスタンス化することです。

元の例では、オブジェクトがレジストリ内に登録された瞬間にインスタンス化されるため、技術的にオートロードは必要ありませんでした。これを行うと、レジストリもそれに割り当てられます。あなたがそのためにそうしているのか、それともあなたにそのようなことが起こったのか、私にはわかりません. 質問にそれが必要だと書いてください。

そのため、レジストリにオートローディング (または遅延ローディング) を導入したい場合、これは少し異なります。あなたのデザインはすでにめちゃくちゃになっているので、さらにマジックを追加していきましょう。レジストリ コンポーネントのインスタンス化を、最初に使用するまで延期したいと考えています。

レジストリでは、コンポーネントの名前が実際のタイプよりも重要であるため、これはすでにかなり動的で文字列のみです。コンポーネントの作成を遅らせるために、クラスは登録時ではなく、アクセス時に作成されます。__getこれは、新しいタイプのレジストリを必要とする機能を利用することで可能になります。

class LazyRegistry extends Registry
{
    private $defines = [];

    public function registerClass($name, $class)
    {
        $this->defines[$name] = $class;
    }

    public function __get($name) {
        $class = $this->defines[$name];
        return $this->$name = new $class($this);
    }
}

使用例もまったく同じですが、レジストリのタイプが変更されています。

$registry = new LazyRegistry();
$json     = new JsonClassmapLoader('path/registry.json');
$json->register($registry);

echo $registry->Service->invoke();  # Done.

そのため、具体的なサービス オブジェクトの作成は、最初にアクセスされるまで延期されました。ただし、これはまだオートロードではありません。クラス定義のロードは、json ローダー内で既に行われています。すでにすべてのものを動的で魔法のようにすることは結果ではありませんが、そうではありません。オブジェクトが最初にアクセスされた瞬間に起動する各クラスのオートローダーが必要です。たとえば、実際には、使用されているかどうかは気にしないため、アプリケーションに腐敗したコードが存在する可能性があり、気付かれないまま永久に残る可能性があることを望んでいます。しかし、それをメモリにロードしたくありません。

spl_autoload_registerオートローディングでは、どのオートローダー機能を使用できるかを知っておく必要があります。これが一般的に役立つ理由はたくさんありますが (たとえば、サードパーティのパッケージを使用していると想像してください)、この動的な魔法の箱はRegistry、仕事に最適なツールです。(時期尚早の最適化を行わない) 簡単な解決策は、レジストリ定義にある各クラスに対して 1 つのオートローダー関数を登録することです。これには新しいタイプのローダーが必要で、オートローダー機能はわずか 2 行程度のコードです。

class LazyJsonClassmapLoader extends JsonClassmapLoader
{
    protected function define($class, $path) {

        $autoloader = function ($classname) use ($class, $path) {

            if ($classname === $class) {
                require($path);
            }
        };

        spl_autoload_register($autoloader);
    }
}

使用例は、ローダーのタイプだけで、あまり変更されていません。

$registry = new LazyRegistry();
$json     = new LazyJsonClassmapLoader('path/registry.json');
$json->register($registry);

echo $registry->Service->invoke(); # Done.

今、あなたは地獄のように怠け者になることができます。つまり、実際にコードを再度変更することになります。それらのファイルを実際にその特定のディレクトリに配置する必要性をリモートにしたいからです。ああ、それはあなたが求めていたものなので、ここに置いておきます。

それ以外の場合は、最初のアクセスでインスタンスを返す callable を使用してレジストリを構成することを検討してください。それは通常物事をより柔軟にします。オートローディングは - 示されているように - 実際にディレクトリベースのアプローチを離れることができれば、コードが具体的にパッケージ化されている場所 (http://www.getcomposer.org/) を気にする必要はありません。

完全なコード例全体 (registry.jsonおよびなしtest.class.php):

class Registry
{
    public function registerClass($name, $class) {
        $this->$name = new $class($this);
    }
}

class LazyRegistry extends Registry
{
    private $defines = [];

    public function registerClass($name, $class) {
        $this->defines[$name] = $class;
    }

    public function __get($name) {
        $class = $this->defines[$name];
        return $this->$name = new $class($this);
    }
}

interface Register
{
    public function register(Registry $registry);
}

class JsonClassmapLoader implements Register
{
    private $file;

    public function __construct($file) {

        $this->file = $file;
    }

    public function register(Registry $registry) {

        $definitions = $this->loadDefinitionsFromFile();

        foreach ($definitions as $name => $definition) {
            $class = $definition->class;
            $path  = dirname($this->file) . '/' . $definition->file;

            $this->define($class, $path);

            $registry->registerClass($name, $class);
        }
    }

    protected function define($class, $path) {

        if (!class_exists($class)) {
            require($path);
        }
    }

    protected function loadDefinitionsFromFile() {

        $json = file_get_contents($this->file);
        return json_decode($json);
    }
}

class LazyJsonClassmapLoader extends JsonClassmapLoader
{
    protected function define($class, $path) {

        $autoloader = function ($classname) use ($class, $path) {

            if ($classname === $class) {
                require($path);
            }
        };

        spl_autoload_register($autoloader);
    }
}

$registry = new LazyRegistry();
$json     = new LazyJsonClassmapLoader('path/registry.json');
$json->register($registry);

echo $registry->Service->invoke(); # Done.

これが役立つことを願っていますが、これは主にサンドボックスでプレイしているため、遅かれ早かれそれを粉砕するでしょう. あなたが実際に学びたいのは、制御の反転、依存性注入、そして依存性注入コンテナーについてです。

あなたが持っているレジストリは、ある種の匂いです。それはすべて魔法とダイナミックに完全に満ちています。これは、開発やシステムに「プラグイン」を配置するのに便利だと思うかもしれませんが (拡張は簡単です)、オブジェクトの数を少なく保つ必要があります。

Magic はデバッグが難しい場合があるため、直接の構成の問題を防ぐために、最初に json ファイルの形式が適切かどうかを確認することをお勧めします。

また、各コンストラクターに渡されるレジストリ オブジェクトは 1 つのパラメーターではなく、動的な量のパラメーターを表すことも考慮してください。これにより、遅かれ早かれ副作用が発生し始めます。レジストリを使いすぎている場合は、多ければ多いほど早くなります。設計上、これにはすでに欠陥があるため、これらの種類の副作用はメンテナンスに多くの費用がかかります。そのため、ハードワーク、回帰のための重い統合テストなどでしか制御できません.

しかし、あなた自身の経験を作ってください、それはあなたが後で私に言うことではなく、ただの見通しです 私はそれに気づきませんでした.

于 2012-09-22T10:39:00.597 に答える
2

2番目の質問:__ autoloadの使用は推奨されておらず、spl_autoload_registerに置き換える必要があります。オートローダーが行うべきことは、名前空間とクラスを分割することです。

function __autoload( $classname )
{    
  if( class_exists( $classname, false ))
    return true;

  $classparts = explode( '\\', $classname );
  $classfile = '/' . strtolower( array_pop( $classparts )) . '.php';
  $namespace = implode( '\\', $classparts );

  // at this point you have to decide how to process further

}

ファイル構造に応じて、名前空間とクラス名に基づいて絶対パスを作成することをお勧めします。

define('ROOT_PATH', __DIR__);

function __autoload( $classname )
{    
  if( class_exists( $classname, false ))
    return true;

  $classparts = explode( '\\', $classname );
  $classfile = '/' . strtolower( array_pop( $classparts )) . '.php';
  $namespace = implode( '\\', $classparts );

  $filename = ROOT_PATH . '/' . $namespace . $classfile;
  if( is_readble($filename))
    include_once $filename;
}

名前空間がパスの一部であるPSR0アプローチを採用しました。

于 2012-09-22T09:25:32.180 に答える