2012-09-20 51 views
4

我正在更新我剛纔寫的框架。我想採用新的標準,如命名空間和使用自動加載功能。在php中的基本自動加載和命名空間

現在我的框架有一個非常基本的,但功能自動加載功能,看起來像這樣:

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類似的功能來訪問其他文件中的特定類,這是我需要的功能。

我需要/想要完成的是使用自動加載器功能,但該功能不限於單個文件夾,而是能夠瀏覽多個文件夾並實例化所需的類。

我也只是一些測試文件去,這裏是我當前的文件結構:

File Structure

對於MyClass2我:

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

對於MyClass1的我:

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

而對於Autoload我有:

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文件,所以我想知道我做錯了,其次的方式,有沒有像我的第一個「自動加載」功能的方式不需要在自動加載文件中指定名稱空間並將其分配給我的註冊表?

對不起,如此漫長的問題,但任何幫助,非常感謝。

+0

快速提示,如果您要在自動加載器中使用class_exists,請確保您使用autoload類參數設置爲false來調用它。否則它會遞歸地觸發自動加載器並炸燬你的臉。 – GordonM

回答

7

我在這裏看到兩件事。

第一個讓你的問題有點複雜。你想使用命名空間,但你當前的配置是通過文件系統。到目前爲止,類定義文件的文件名不包含名稱空間。所以你不能像你實際上那樣繼續下去。

第二個是你沒有PHP自動加載所涵蓋的內容,你只需加載一組定義好的類並將它註冊到註冊表中。

我不太確定您是否需要PHP自動加載。當然,它可能看起來很有希望讓你們把它們放在一起。解決第一點可能會幫助你解決後面的問題,所以我建議先從頭開始。

讓我們使隱藏的依賴關係更明顯。在您當前的設計中,您有三件事:

  1. 對象在註冊表中註冊的名稱。
  2. 包含類定義的文件名。
  3. 類本身的名稱。

2.和3.的值在一箇中,您從文件名解析類本身的名稱。正如所寫,名稱空間現在使這變得複雜。該解決方案很簡單,而不是從目錄列表中讀取,您可以從包含此信息的文件中讀取。一個輕量級的配置文件格式是JSON:

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

現在,這包含三個所需的依賴,因爲文件名也是已知的由一個名稱的類註冊到註冊表中。

然後允許在註冊表中註冊類:

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. 

這個例子也很簡單,它的工作原理很簡單。所以第一個問題就解決了。

第二個問題是自動加載。這個當前的變體和你以前的系統也隱藏了其他的東西。有兩件重要的事情要做。一個是實際加載類定義,另一個是實例化對象。

在您的原始示例中,技術上自動加載不是必需的,因爲對象在註冊表中註冊的那一刻也會實例化。你這樣做也可以將註冊表分配給它。我不知道你是否僅僅因爲這樣做,或者如果這只是發生在你身上。你在你的問題中寫下你需要的。

所以,如果你想自動加載到你的註冊表(或延遲加載),這將有所不同。由於您的設計已經搞亂了,讓我們繼續在頂部添加更多魔術。您希望將註冊表組​​件的實例推遲到第一次使用時。

由於在註冊表中組件的名稱比它的實際類型更重要,所以它已經非常動態且只有一個字符串。爲了推遲組件創建,在註冊但未訪問時不創建該類。這是可能通過利用__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,它只是這項工作的完美工具。一個直接的解決方案(並且不做任何過早的優化)是爲註冊表定義中的每個類註冊一個自動加載器函數。這則需要一個新的類加載器的磁帶自動加載機的功能僅僅是兩行代碼左右:

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. 

現在你可以像地獄一樣懶惰。這意味着要再次更改代碼。因爲您想遠程將這些文件實際放入特定目錄的必要性。等等,這就是你要求的,所以我們把它留在這裏。

否則,請考慮使用將在第一次訪問時返回實例的可調用項來配置注​​冊表。這通常會使事情更加靈活。自動加載(如圖所示)與此無關,如果您實際上可以離開基於目錄的方法,則您不再關心代碼在具體位置打包的位置(http://www.getcomposer.org/)。

在充滿整個代碼示例(不registry.jsontest.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. 

我希望這是有幫助的,但是這主要是打在沙箱中,你將粉碎的早晚。你真正想學的是控制反轉,依賴注入,然後是依賴注入容器。

你擁有的註冊表是某種味道。這一切都充滿魔力和活力。您可能認爲這對於開發或在系統中具有「插件」很酷(很容易擴展),但是您應該將其中的對象數量保持較低。

魔法可能很難調試,因此如果您首先想要防止出現第一手配置問題,那麼您可能需要檢查json文件的格式。

還要考慮傳遞給每個構造函數的註冊表對象不是一個參數,而是表示動態數量的參數。這將開始遲早會產生副作用。如果你使用的註冊表太多,那麼越快。這些副作用會使您的維護費用增加很多,因爲通過設計這個已經存在缺陷,所以您只能通過艱苦的工作,對於迴歸的重度集成測試等來控制它。

但是,讓您自己的體驗,這只是一些觀點,不是你後來告訴我,我沒有注意到它。

+1

這是我在Stack Overflow上獲得的最佳迴應。謝謝。 – Peter

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方法,其中命名空間是路徑的一部分。