PHP的PSR-0標準利用namespace來做autoloading

介紹PSR-0之前,先來說說命名空間(NameSpace)和Autoloading吧。

NameSpace(命名空間)

namespace是PHP5.3版本加入的新特性,用來解決在編寫類庫或應用程序時創建可重用的代碼如類或函數時碰到的兩類問題:

1.用戶編寫的代碼與PHP內部的類/函數/常量或第三方類/函數/常量之間的名字衝突。
2.爲很長的標識符名稱(通常是爲了緩解第一類問題而定義的)創建一個別名(或簡短)的名稱,提高源代碼的可讀性。

PHP 命名空間中的元素使用了類似文件系統的原理。例如,類名可以通過三種方式引用:
1.非限定名稱,或不包含前綴的類名稱,例如 $a=new foo(); 或 foo::staticmethod();。如果當前命名空間是 currentnamespace,foo 將被解析爲 currentnamespace\foo。如果使用 foo 的代碼是全局的,不包含在任何命名空間中的代碼,則 foo 會被解析爲foo。 警告:如果命名空間中的函數或常量未定義,則該非限定的函數名稱或常量名稱會被解析爲全局函數名稱或常量名稱。詳情參見 使用命名空間:後備全局函數名稱/常量名稱。
2.限定名稱,或包含前綴的名稱,例如 $a = new subnamespace\foo(); 或 subnamespace\foo::staticmethod();。如果當前的命名空間是 currentnamespace,則 foo 會被解析爲 currentnamespace\subnamespace\foo。如果使用 foo 的代碼是全局的,不包含在任何命名空間中的代碼,foo 會被解析爲subnamespace\foo。
3.完全限定名稱,或包含了全局前綴操作符的名稱,例如, $a = new \currentnamespace\foo(); 或 \currentnamespace\foo::staticmethod();。在這種情況下,foo 總是被解析爲代碼中的文字名(literal name)currentnamespace\foo。

另外注意訪問任意全局類、函數或常量,都可以使用完全限定名稱,例如 \strlen() 或 \Exception 或 \INI_ALL。

[php] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. <?php  
  2. use My\Full\Classname as Another, My\Full\NSname;  
  3.   
  4. $obj = new Another; // 實例化一個 My\Full\Classname 對象  
  5. $obj = new \Another; // 實例化一個Another對象  
  6. $obj = new Another\thing; // 實例化一個My\Full\Classname\thing對象  
  7. $obj = new \Another\thing; // 實例化一個Another\thing對象  
  8.   
  9. $a = \strlen('hi'); // 調用全局函數strlen  
  10. $b = \INI_ALL; // 訪問全局常量 INI_ALL  
  11. $c = new \Exception('error'); // 實例化全局類 Exception  
  12. ?>  

以上是namespace的簡要介紹,對此不瞭解的同學還是把細讀文檔吧。在衆多新的PHP項目中,namespace已經被普遍使用了。尤其是伴隨Composer的流行,它已經是很有必要去了解的特性了。


Autoload(自動加載)

很多開發者寫面向對象的應用程序時對每個類的定義建立一個 PHP 源文件。一個很大的煩惱是不得不在每個腳本開頭寫一個長長的包含(require, include)文件列表(每個類一個文件)。

而Autoload,就是解決這個問題的,通過定義的一個或一系列autoload函數,它會在試圖使用尚未被定義的類時自動調用。通過調用autoload函數,腳本引擎在 PHP 出錯失敗前有了最後一個機會加載所需的類。這個autoload函數可以是默認的__autoload(),如下:

[php] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. <?php  
  2. function __autoload($class_name) {  
  3.     require_once $class_name . '.php';  
  4. }  
  5.   
  6. $obj  = new MyClass();  
也可以採用更靈活的方式,通過spl_autoload_register()來定義我們自己的__autoload()函數:
[php] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. <?php  
  2. function my_autoload($class_name) {  
  3.     require_once $class_name . '.php';  
  4. }  
  5. spl_autoload_register("my_autoload");  
  6. $obj  = new MyClass();  
以上代碼將my_autoload()函數註冊到__autoload棧中,從而取到__autoload()函數(注意__autoload()函數將不再起作用,但可以顯式的將其註冊到__autoload棧)。注意到剛纔提到了__autoload棧,意味着我們可以註冊多個autoload函數,根據註冊的順序依次加載(通過spl_autoload_register的第三個參數可以改變這一順序)。

在這裏我們展示了autoload函數最簡單的例子,當然通過一些規則的設置,也可以勝任實際環境中許多複雜的情況。

但是如果我們的項目依賴某些其他的項目,本來大家在各自獨立的加載規則下能順利運行,但融合在一起可能就不妙了。那麼能否有一種通用的加載規則來解決這個問題?


PSR-0

PSR是由PHP Framework Interoperability Group(PHP通用性框架小組)發佈的一系列標準/規範,目前包括了PSR-0~PSR-4共4個,而PSR-0就是其中的自動加載標準(其後的PSR-4稱爲改進的自動加載的標準,是PSR-0的補充。PSR-0使用更廣泛)。https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md

下面描述了具體互操作性的自動加載所必須的條件:

強制要求

  • 一個完全合格的namespace和class必須符合這樣的結構 \<Vendor Name><Namespace>*<Class Name>
  • 每個namespace必須有一個頂層的namespace("Vendor Name"提供者名字)
  • 每個namespace可以有多個子namespace
  • 當從文件系統中加載時,每個namespace的分隔符要轉換成 DIRECTORY_SEPARATOR(操作系統路徑分隔符)
  • 在CLASS NAME(類名)中,每個下劃線(_)符號要轉換成DIRECTORY_SEPARATOR。在namespace中,下劃線(_)符號是沒有(特殊)意義的。
  • 當從文件系統中載入時,合格的namespace和class一定是以 .php 結尾的
  • verdor name,namespaces,class名可以由大小寫字母組合而成(大小寫敏感的)
除此之外可能還會遵循這個規則:如果文件不存在則返回false。

例子

  • \Doctrine\Common\IsolatedClassLoader => /path/to/project/lib/vendor/Doctrine/Common/IsolatedClassLoader.php
  • \Symfony\Core\Request => /path/to/project/lib/vendor/Symfony/Core/Request.php
  • \Zend\Acl => /path/to/project/lib/vendor/Zend/Acl.php
  • \Zend\Mail\Message => /path/to/project/lib/vendor/Zend/Mail/Message.php

NameSpace和Class Name中的下劃線

  • \namespace\package\Class_Name => /path/to/project/lib/vendor/namespace/package/Class/Name.php
  • \namespace\package_name\Class_Name => /path/to/project/lib/vendor/namespace/package_name/Class/Name.php
將下劃線轉換成DIRECTORY_SEPARATOR實際上是出於兼容PHP5.3之前的版本的考慮

實現的範例

下面是一個按照如上標準進行自動加載的簡單範例:

[php] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. <?php  
  2. function autoload($className)  
  3. {  
  4.     //這裏的$className一般是用namespace的方式來引用的,文章開頭已有介紹  
  5.     //去除$className左邊的'\' 這是PHP5.3的一個bug,詳見https://bugs.php.net/50731  
  6.     $className = ltrim($className'\\');  
  7.     $fileName  = '';  
  8.     $namespace = '';  
  9.     //找到最後一個namespace分隔符的位置  
  10.     if ($lastNsPos = strrpos($className'\\')) {  
  11.         $namespace = substr($className, 0, $lastNsPos);  
  12.         $className = substr($className$lastNsPos + 1);  
  13.         $fileName  = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;  
  14.     }  
  15.     $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';  
  16.   
  17.     require $fileName;  
  18. }  

SplClassLoader 的實現

下面有一個SplClassLoader 的實現範例,如果你遵循瞭如上的標準,可以用它來進行自動加載。這也是目前PHP5.3推薦的類加載標準。http://gist.github.com/221634
[php] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. <?php  
  2.    
  3. /* 
  4.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
  5.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
  6.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
  7.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
  8.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
  9.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
  10.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
  11.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
  12.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
  13.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
  14.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
  15.  * 
  16.  * This software consists of voluntary contributions made by many individuals 
  17.  * and is licensed under the MIT license. For more information, see 
  18.  * <http://www.doctrine-project.org>. 
  19.  */  
  20.    
  21. /** 
  22.  * SplClassLoader implementation that implements the technical interoperability 
  23.  * standards for PHP 5.3 namespaces and class names. 
  24.  * 
  25.  * http://groups.google.com/group/php-standards/web/psr-0-final-proposal?pli=1 
  26.  * 
  27.  *     // Example which loads classes for the Doctrine Common package in the 
  28.  *     // Doctrine\Common namespace. 
  29.  *     $classLoader = new SplClassLoader('Doctrine\Common', '/path/to/doctrine'); 
  30.  *     $classLoader->register(); 
  31.  * 
  32.  * @license http://www.opensource.org/licenses/mit-license.html  MIT License 
  33.  * @author Jonathan H. Wage <[email protected]> 
  34.  * @author Roman S. Borschel <[email protected]> 
  35.  * @author Matthew Weier O'Phinney <[email protected]> 
  36.  * @author Kris Wallsmith <[email protected]> 
  37.  * @author Fabien Potencier <[email protected]> 
  38.  */  
  39. class SplClassLoader  
  40. {  
  41.     private $_fileExtension = '.php';  
  42.     private $_namespace;  
  43.     private $_includePath;  
  44.     private $_namespaceSeparator = '\\';  
  45.    
  46.     /** 
  47.      * Creates a new <tt>SplClassLoader</tt> that loads classes of the 
  48.      * specified namespace. 
  49.      *  
  50.      * @param string $ns The namespace to use. 
  51.      */  
  52.     public function __construct($ns = null, $includePath = null)  
  53.     {  
  54.         $this->_namespace = $ns;  
  55.         $this->_includePath = $includePath;  
  56.     }  
  57.    
  58.     /** 
  59.      * Sets the namespace separator used by classes in the namespace of this class loader. 
  60.      *  
  61.      * @param string $sep The separator to use. 
  62.      */  
  63.     public function setNamespaceSeparator($sep)  
  64.     {  
  65.         $this->_namespaceSeparator = $sep;  
  66.     }  
  67.    
  68.     /** 
  69.      * Gets the namespace seperator used by classes in the namespace of this class loader. 
  70.      * 
  71.      * @return void 
  72.      */  
  73.     public function getNamespaceSeparator()  
  74.     {  
  75.         return $this->_namespaceSeparator;  
  76.     }  
  77.    
  78.     /** 
  79.      * Sets the base include path for all class files in the namespace of this class loader. 
  80.      *  
  81.      * @param string $includePath 
  82.      */  
  83.     public function setIncludePath($includePath)  
  84.     {  
  85.         $this->_includePath = $includePath;  
  86.     }  
  87.    
  88.     /** 
  89.      * Gets the base include path for all class files in the namespace of this class loader. 
  90.      * 
  91.      * @return string $includePath 
  92.      */  
  93.     public function getIncludePath()  
  94.     {  
  95.         return $this->_includePath;  
  96.     }  
  97.    
  98.     /** 
  99.      * Sets the file extension of class files in the namespace of this class loader. 
  100.      *  
  101.      * @param string $fileExtension 
  102.      */  
  103.     public function setFileExtension($fileExtension)  
  104.     {  
  105.         $this->_fileExtension = $fileExtension;  
  106.     }  
  107.    
  108.     /** 
  109.      * Gets the file extension of class files in the namespace of this class loader. 
  110.      * 
  111.      * @return string $fileExtension 
  112.      */  
  113.     public function getFileExtension()  
  114.     {  
  115.         return $this->_fileExtension;  
  116.     }  
  117.    
  118.     /** 
  119.      * Installs this class loader on the SPL autoload stack. 
  120.      */  
  121.     public function register()  
  122.     {  
  123.         spl_autoload_register(array($this'loadClass'));  
  124.     }  
  125.    
  126.     /** 
  127.      * Uninstalls this class loader from the SPL autoloader stack. 
  128.      */  
  129.     public function unregister()  
  130.     {  
  131.         spl_autoload_unregister(array($this'loadClass'));  
  132.     }  
  133.    
  134.     /** 
  135.      * Loads the given class or interface. 
  136.      * 
  137.      * @param string $className The name of the class to load. 
  138.      * @return void 
  139.      */  
  140.     public function loadClass($className)  
  141.     {  
  142.         if (null === $this->_namespace || $this->_namespace.$this->_namespaceSeparator === substr($className, 0, strlen($this->_namespace.$this->_namespaceSeparator))) {  
  143.             $fileName = '';  
  144.             $namespace = '';  
  145.             if (false !== ($lastNsPos = strripos($className$this->_namespaceSeparator))) {  
  146.                 $namespace = substr($className, 0, $lastNsPos);  
  147.                 $className = substr($className$lastNsPos + 1);  
  148.                 $fileName = str_replace($this->_namespaceSeparator, DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;  
  149.             }  
  150.             $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . $this->_fileExtension;  
  151.    
  152.             require ($this->_includePath !== null ? $this->_includePath . DIRECTORY_SEPARATOR : '') . $fileName;  
  153.         }  
  154.     }  
  155. }  

看到這裏你可能還有疑問,比如psr-0中給出的例子

\Symfony\Core\Request => /path/to/project/lib/vendor/Symfony/Core/Request.php

在=>號之前是我們要去加載的類,而後面的部分是這個文件在文件系統中的路徑(我們實際加載的是這個)。在加載Symfony\Core\Request時,並沒有同時給出實際路徑來,那麼是如何得到這個映射的呢?在PSR-0標準中並沒有對/path/to/project/lib路徑的強制要求,而是可以有自己的實現。一般有兩種方式,一種就是使用php配置中的include_path(上面給出的例子有使用這個方式來做),第二種就是自己去註冊這種映射,可以選用其中一種,或者兩種都兼容。下面就來看看在Symfony中是怎麼做的?

[php] view plain copy 在CODE上查看代碼片派生到我的代碼片
  1. <?php  
  2. require_once '/path/to/src/Symfony/Component/ClassLoader/ClassLoader.php';  
  3.   
  4. use Symfony\Component\ClassLoader\ClassLoader;  
  5.   
  6. $loader = new ClassLoader();  
  7.   
  8. // to enable searching the include path (eg. for PEAR packages)  
  9. $loader->setUseIncludePath(true);  
  10.   
  11. // ... register namespaces and prefixes here - see below  
  12.   
  13. $loader->register();  
  14.   
  15. // register a single namespaces  
  16. $loader->addPrefix('Symfony', __DIR__.'/vendor/symfony/symfony/src');  
此時就將 Symfony 這個namespace 註冊到了一個真實的路徑上,Symfony的子命名空間也可以使用這個路徑了。

完。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章