介紹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
- use My\Full\Classname as Another, My\Full\NSname;
- $obj = new Another; // 實例化一個 My\Full\Classname 對象
- $obj = new \Another; // 實例化一個Another對象
- $obj = new Another\thing; // 實例化一個My\Full\Classname\thing對象
- $obj = new \Another\thing; // 實例化一個Another\thing對象
- $a = \strlen('hi'); // 調用全局函數strlen
- $b = \INI_ALL; // 訪問全局常量 INI_ALL
- $c = new \Exception('error'); // 實例化全局類 Exception
- ?>
以上是namespace的簡要介紹,對此不瞭解的同學還是把細讀文檔吧。在衆多新的PHP項目中,namespace已經被普遍使用了。尤其是伴隨Composer的流行,它已經是很有必要去了解的特性了。
Autoload(自動加載)
很多開發者寫面向對象的應用程序時對每個類的定義建立一個 PHP 源文件。一個很大的煩惱是不得不在每個腳本開頭寫一個長長的包含(require, include)文件列表(每個類一個文件)。
而Autoload,就是解決這個問題的,通過定義的一個或一系列autoload函數,它會在試圖使用尚未被定義的類時自動調用。通過調用autoload函數,腳本引擎在 PHP 出錯失敗前有了最後一個機會加載所需的類。這個autoload函數可以是默認的__autoload(),如下:
- <?php
- function __autoload($class_name) {
- require_once $class_name . '.php';
- }
- $obj = new MyClass();
- <?php
- function my_autoload($class_name) {
- require_once $class_name . '.php';
- }
- spl_autoload_register("my_autoload");
- $obj = new MyClass();
在這裏我們展示了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名可以由大小寫字母組合而成(大小寫敏感的)
例子
- \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
實現的範例
下面是一個按照如上標準進行自動加載的簡單範例:
- <?php
- function autoload($className)
- {
- //這裏的$className一般是用namespace的方式來引用的,文章開頭已有介紹
- //去除$className左邊的'\' 這是PHP5.3的一個bug,詳見https://bugs.php.net/50731
- $className = ltrim($className, '\\');
- $fileName = '';
- $namespace = '';
- //找到最後一個namespace分隔符的位置
- if ($lastNsPos = strrpos($className, '\\')) {
- $namespace = substr($className, 0, $lastNsPos);
- $className = substr($className, $lastNsPos + 1);
- $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
- }
- $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
- require $fileName;
- }
-
SplClassLoader 的實現
看到這裏你可能還有疑問,比如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
- require_once '/path/to/src/Symfony/Component/ClassLoader/ClassLoader.php';
- use Symfony\Component\ClassLoader\ClassLoader;
- $loader = new ClassLoader();
- // to enable searching the include path (eg. for PEAR packages)
- $loader->setUseIncludePath(true);
- // ... register namespaces and prefixes here - see below
- $loader->register();
- // register a single namespaces
- $loader->addPrefix('Symfony', __DIR__.'/vendor/symfony/symfony/src');
完。