Namespaces也是PHP5.3才加入的功能,最主要的目的是用來組織類別與函數,並且避免名稱的衝突。
autoload則是從PHP5開始就有的功能。為了類別的組織方便,許多人都是以一個類別一個PHP檔的方式來開發,但是如果在使用類別時還需要一個一個include,那就太麻煩了。所以PHP設計了autoload機制,讓開發者自己定義怎樣載入類別的檔案。
類別、組織的命名方式是隨個人喜好的,到了PHP5.3還會加入Namespace,所以實作autoload的時候,除了自己的命名規則,也需要考慮到Namespaces。
參考:
* PHP: Namespaces - Manual
* PHP: Autoload Classes - Manual
不像Java,PHP一直沒有統一的命名規則,所以...需要看情況調整怎麼做autoloading。在專案中會碰到幾個狀況:
這些是在還沒Namespace機制時,就已經會有的問題。要再加上Namespace一起考量的話就更複雜了。還是先看看Namespace的怎麼使用的:
* Namespaces
首先來看看語法,在想要定義namespace時:
namespace namespace1\sub1
class a {}
function b(){}
這樣就定義了在namespace1\sub1這個namespace下的類別a以及函數b。如果要使用,可以:
use namespace1\sub1\a;
use namespace1\sub1\b;
$a = new a;
$b = b();
或是加上完整的namespace來識別:
$a = new namespace1\sub1\a;
$b = namespace1\sub1\b();
如果namespace很長,也可以給他先取個別名來用:
use namespace1\sub1 as ns1;
$a = new ns1\a;
$b = new ns1\b();
如果一些類別沒有使用namespace來定義,那他就放在global namespace。這樣要用時,需要在前面加上\,代表這是定義在global namespace中的東西。(下面例子是兩個檔案)
//1-5b.php
<?php
class a {
function __construct() {
echo 'a'."\n";
}
}
function f(){
echo 'f'."\n";
}
//1-5a.php
<?php
namespace namespace1\sub1;
require '1-5b.php';
class a {
function __construct() {
echo 'namespace1\\sub1\\a'."\n";
}
}
class b {
function abc() {
$a1 = new \a;
$a2 = new a;
\f();
f();
}
}
function f(){
echo 'namespace1\\sub1\\f'."\n";
}
$b = new b;
$b->abc();
執行結果:
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-5a.php
a
namespace1\sub1\a
f
在前面的語法中,namespace是以檔案為單位的。如果需要在一個檔案中宣告多的namespace,那最好把同一個namespace的程式包裝在一個大括號中:
<?php
namespace {
class a {
function __construct() {
echo 'a'."\n";
}
}
function f(){
echo 'f'."\n";
}
}
namespace namespace1\sub1 {
class a {
function __construct() {
echo 'namespace1\\sub1\\a'."\n";
}
}
class b {
function abc() {
$a1 = new \a;
$a2 = new a;
\f();
f();
}
}
function f(){
echo 'namespace1\\sub1\\f'."\n";
}
$b = new b;
$b->abc();
}
如果程式都有明確的namespace,其實並不一定需要使用大括號,不過這樣會無法定義global namespace的程式,因為namespace必須使用在程式開頭,這樣之後的程式就不是在global namespace了。另外,不使用大括號就在同一個檔案中定義多個namespace,分界也不是那麼明顯,所以還是用大括號來把同樣namespace的程式集合在一起比較好。
不過雖然可以在同一個檔案中定義多個namespace,為了管理以及autoloading,建議還是一個檔案一個namespace就好。
跟Java有一點不一樣,就是PHP的namespace沒有*通配符,所以如果只想用類別名稱來使用定義在其他namespace的類別,在use的語句中一定要包含完整的namespace與類名。(之前被這個卡到過)要稍微省事的話,還是得使用as來定義別名,然後透過別名來使用。像這樣:
<?php
namespace namespace1\sub1 {
class a {
function show() {
echo "namespace:\t".__NAMESPACE__."\n";
echo "class: \t".__CLASS__."\n";
echo "method: \t".__METHOD__."\n";
}
}
}
namespace namespace1\sub2 {
use namespace1\sub1\a;
class b {
function show() {
echo "[".__CLASS__."]\n";
$a = new a;
$a->show();
}
}
$b = new b;
$b->show();
}
namespace namespace2\sub1 {
use namespace1\sub1 as ns1;
class c {
function show() {
echo "[".__CLASS__."]\n";
$a = new ns1\a;
$a->show();
}
}
$c = new c;
$c->show();
}
namespace1\sub1的類別a,定義了show()方法,會在執行時顯示自己的namespace、class、method資訊。在namespace1\sub2\b以及namespace2\sub1\c中都會使用到這個類別的show()方法。
執行結果:
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-5d.php
[namespace1\sub2\b]
namespace: namespace1\sub1
class: namespace1\sub1\a
method: namespace1\sub1\a::show
[namespace2\sub1\c]
namespace: namespace1\sub1
class: namespace1\sub1\a
method: namespace1\sub1\a::show
PHP定義了幾個常數,透過他可以知道一些資訊。__NAMESPACE__會顯示當前的namespace,__CLASS__會顯示當前的class,__METHOD__則是當前執行的method,從這些可以看出,namespace資訊,並不會受到別名的影響,這對於使用autoloading來說是很重要的。
* class autoloading
PHP的autoloading機制,原理其實很簡單。在程式產生一個類別實體時,如果找不到類別定義,就會來呼叫使用者定義的autoloading函數,把完整的類別名稱當做參數丟給他。在函數中,需要做的事情就是,依照類別名稱,include或require定義類別的檔案。
如果要配合namespace,最簡單明瞭的對應方式,是利用目錄來對應namespace。假設所有類別檔案都放在vendors目錄,副檔名是.php那:
class a {
}
就是vendors/a.php。
namespace namespace1\sub1;
class b {
}
就是vendors/namespace1/sub1/b.php等等,依此類推。只要依照個個規則,寫好一個autoloading函數,就可以依照類別名稱去引入正確的類別程式檔。
其實spl中有定義好預設的autoload函數,他可以依照上述的規則找到類別程式檔,所以用這個例子來看:
類別檔的目錄組織:
vendors
├── a.php
├── namespace1
│ ├── sub1
│ │ └── b.php
│ └── sub2
│ └── c.class.php
└── namespace2
└── sub1
└── demo
└── d.php
每個class中都在constructor顯示自己的類別名稱,這樣在new時會顯示類別資訊:
function __construct() {
echo __CLASS__."\n";
}
然後用預設的spl_autoload來自動載入看看:
<?php
define('CLASS_DIR', 'vendors/');
set_include_path(get_include_path().PATH_SEPARATOR.CLASS_DIR);
spl_autoload_extensions(".php,.class.php");
spl_autoload_register();
$a = new a;
$b = new namespace1\sub1\b;
$c = new namespace1\sub2\c;
$d = new namespace2\sub1\demo_d;
執行的結果:
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-5e.php
a
namespace1\sub1\b
namespace1\sub2\c
PHP Fatal error: spl_autoload(): Class namespace2\sub1\demo_d could not be loaded in /Users/fillano/builds/ironman6/1-5e.php on line 9
結果最後一個會失敗,這是因為demo_d類別是模仿Zend Framework的命名規則,類別名稱如果含有底線的話,例如demo_c,則程式檔是demo/c.php。如果碰到像這樣的例外規則,內建的spl_autoload就無法應付,這時候就需要自己寫,然後呼叫spl_autoload_register來加入。
加入一個自定的autoload函數來處理像這樣特別的命名規則:
<?php
define('CLASS_DIR', 'vendors/');
set_include_path(get_include_path().PATH_SEPARATOR.CLASS_DIR);
spl_autoload_extensions(".php,.class.php");
spl_autoload_register();
spl_autoload_register(function($class) {
$class = str_replace('_', '/', $class);
$class = str_replace('\\', '/', $class);
echo $class."\n";//如果跑到這裡的話,秀一下結果
include $class.".php";
}, false);
$a = new a;
$b = new namespace1\sub1\b;
$c = new namespace1\sub2\c;
$d = new namespace2\sub1\demo_d;
執行結果:
Feng-Hsu-Pingteki-MacBook-Air:ironman6 fillano$ php 1-5f.php
a
namespace1\sub1\b
namespace1\sub2\c
namespace2/sub1/demo/d
namespace2\sub1\demo_d
OK,順利完成,就先這樣吧,好像花太多時間玩autoload了