Okay,我們可以透過 include
, include_once
, require
或是 require_once
來將檔案引入到我們目前正在編寫的這個檔案,我們也知道習慣上我們會將一個 Class 存放在單一的 PHP 檔案中,例如 Member.php 相對於 Member 這個 Class,當我們的程式需要用到這個 Class 的時候就可以用上述的方法來引用這個 Class 以供後續操作。
//index.php
include_once "Member.php";
$member = new Member();
$member->getMemberList();
完美!!! 但是如果今天我們有一卡車的 Classes 要引入呢?
//index.php
include_once "Member.php";
include_once "Mailsender.php";
include_once "Validate.php";
inlcude_once "Other.php";
What the #$%^# ... Okay! 夠了! 這看上去實在是不怎麼舒服,光這些引入就佔了一堆行數,我都還沒開始寫呢! 那有沒有辦法可以解決這個問題呢?有的 - Autoload !!!
PHP Autoload
PHP Autoload 機制可以讓我們能夠在需要這個物件的時候才去真正的引入這個 Class,這個動作就是常聽到的 Lazyload 延遲載入,在這裡我們不會談論實際上 PHP 怎麼去實現這個機制,只階段性的介紹 PHP 內幾種 Autoload 的做法。
- __autoload
- spl_autoload
- autoload 與 namespace
__autoload
PHP5 提供了 __autoload()
這個魔術方法實現上述 Autoload 機制,雖然這個方法效能上及方便性並不是非常理想(後續會提到其他做法),讓我們來用這個方法實際寫點東西吧!
為了讓例子看起來比較簡單,我們將不會把資料夾分的太細,以下為我們的根目錄資料夾。
+ classes
+-- Member.php
- autoload.php
- index.php
首先我們看到 classes 資料夾底下的 Member.php。
<?php
//classes/Member.php
class Member
{
public function getMemberList()
{
echo "print member list...<br>";
}
}
是的! 沒什麼特別的,我們在 classes 資料夾底下建立了一個 Member Class 包含了一個 public method getMemberList()
簡單的輸出一串字串 print member list...
接著來到我們的重頭戲 autoload.php 這個檔案
<?php
//autoload.php
function __autoload($className) {
$filename = __DIR__ . "/classes/" . $className . ".php";
if (is_readable($filename)) {
require $filename;
}
}
在這個 function 內,我們首先取得存放 classes 路徑中的檔案名稱 $filename
接著我們用到了 is_readable()
這個 PHP 內建 function 判斷是檔案是否存在及可讀取,然後 require 引入這個檔案。聽起來棒極了,但是你沒有告訴我怎麼使用這個 __autoload()
這個 function 啊!!! 別急,究竟要怎麼讓這個東西動起來,讓我們看下去...
<?php
//index.php
include_once __DIR__ . "/autoload.php";
$member = new Member();
$member->getMemberList();
我們將 autoload.php 引入到我們程式的最一開始,現在當需要使用在 classes 目錄底下(不包含子目錄)的所有 class 都將會自動被載入,你不需要在手動的引入你所需要的 class。
執行 index.php 會輸出一段文字 print member list...
在你的 browser 上,太神奇了!!!
什麼!!! 有件事我忘了跟你說嗎? 呃... __autoload()
不能重覆的被定義,也就是說,當你嘗試引入兩支不一樣的 autoload.php (或許你有其他目錄底下的 class 也想被自動載入...) 的時候,PHP 將會報出錯誤訊息 Fatal error: Cannot redeclare __autoload()...
。
這樣子很不方便耶! 別急,PHP 當然知道要幫你解決這個問題,下一章節將說明。
(你當然可以通過 php.ini 設定 include path 來解決這樣子的問題,不過這有點不明智就是了...)
spl_autoload 與 spl_autoload_register
PHP 在 5.1.2 以後提供了 SPL 這個方便的大玩意,SPL 全名為 Standard PHP Library,顧名思義,提供你 許多 Interface 及 class,目的為解決常見的問題。
spl_autoload
預設實作__autoload()
魔術方法,也就是說,當spl_autoload_register()
沒有定義或是沒有帶任何參數的時候,預設會採用__autoload()
這個 functionspl_autoload_register
spl_autoload_register()
讓你能夠註冊你自定義的 autoload function,當 spl_autoload_register() 有註冊之後,__autoload()
將會失效,你必需自己手動將__autoload()
註冊,才能使用。
廢話不多說,我們來寫點程式。
首先我們新增兩個目錄 first, second 在我們剛剛建立的目錄,同時也個新增兩個 class 在 first 和 second 底下。
+ classes
+-- Member.php
+ first
+-- First.php
+ second
+-- Second.php
- autoload.php
- index.php
讓我們來看一下 First.php 和 Second.php 的內容。
<?php
//first/First.php
class First
{
public function doSomething()
{
echo "I am first class...<br>";
}
}
<?php
//second/Second.php
class Second
{
public function doSomething()
{
echo "I am second class...<br>";
}
}
新增的這兩個 class 內容沒什麼特別的,各有一個 doSomething()
的 method 分別輸出 I am first class...
和 I am second class...
, 接著我們來修改一下我們 index.php。
<?php
//index.php
$first = new First();
$first->doSomething();
$second = new Second();
$second->doSomething();
聰明的你一定會說,你把 autoload.php 的引入拿掉,這一定出錯的啊! 沒錯,所以我們現在要來註冊我們自己定義的 autoload,我們在資料夾底下再新增兩個檔案 firstAutoload.php 和 secondAutoload.php。
+ classes
+-- Member.php
+ first
+-- First.php
+ second
+-- Second.php
- firstAutoload.php
- secondAutoload.php
- autoload.php
- index.php
接著我們來看看這兩個 autoload 裡寫了些什麼。
<?php
//firstAutoload.php
function firstAutoload($className) {
$filename = __DIR__ . "/first/" . $className . ".php";
if (is_readable($filename)) {
require $filename;
}
}
spl_autoload_register('firstAutoload');
<?php
//secondAutoload.php
function secondAutoload($className) {
$filename = __DIR__ . "/second/" . $className . ".php";
if (is_readable($filename)) {
require $filename;
}
}
spl_autoload_register('secondAutoload');
我們定義了自己的 autoload function 之後就使用 spl_autoload_register()
註冊這個 function 的名稱,接著我們在 index.php 裡把這兩個自定義的 autoload 檔案引入。
<?php
//index.php
include_once __DIR__ . "/firstAutoload.php";
include_once __DIR__ . "/secondAutoload.php";
$first = new First();
$first->doSomething();
$second = new Second();
$second->doSomething();
完美的輸出像這樣子的畫面...
I am first class...
I am second class...
眼尖的你一定發現了,我們這樣子每要加入一個目錄放 class 不就要多寫一個 autoload,而且還要引入這些檔案,那不就跟原來一樣,喔不對,還比原來麻煩。
嗯,你說的沒錯,那我們用 foreach 來處理這樣的問題呢?
我們再新增一個檔案 allAutoload.php 在目錄底下,並看看內容寫了些什麼。
<?php
//allAutoload.php
function allAutoload($className)
{
$folders = array(
__DIR__ . "/first/",
__DIR__ ."/second/",
);
foreach ($folders as $folder) {
$fileName = $folder . $className . ".php";
if (is_readable($fileName)) {
require $fileName;
}
}
}
spl_autoload_register('allAutoload');
Okay,我們把我們想要自動載入的目錄都設定在一個 array 裡,接著我們 foreach 這個 array,裡面做的事其實就跟先前的一樣,接著我們用 spl_autoload_register()
註冊這個 function 的名稱,我們來看一下 index.php 做了哪些改變。
<?php
//index.php
include_once __DIR__ . "/allAutoload.php";
$first = new First();
$first->doSomething();
$second = new Second();
$second->doSomething();
這次我們只需要引入一個 allAutoload.php 檔案了,我們又再一次完全的輸出了...
I am first class...
I am second class...
看起來是不錯,不過聰明的你一定又發現了,這樣子我們每次要新增一個 class 目錄或是子目錄的時候,都要去更動我們的 autoload 檔案耶,這樣還是很麻煩呀!
沒關係,是 namespace 該出場的時候了。
autoload 與 namespace
PHP 5.3.0 以後提供了namespace (命名空間) 的功能解決了 class 名稱重覆定義造成衝突的問題,這問題很容易發生在你的 project 內有使用到其他第三方的程式庫的時候。而 PHP FIG 也在它所制定的 PSR-0 定義了依照 namespace 使用 autoload 的方法。
我們先來看一下 簡單的 namespace 使用方法。
<?php
//MyClass.php
namespace MyNamespace;
class MyClass
{
public function doSomething()
{
echo "This is my class...<br>";
}
}
<?php
//index.php
include_once __DIR__ . "/MyClass.php";
$myClass = new \MyNamespace\MyClass();
$myClass->doSomething();
在 MyClass.php 內,我們看到 namespace MyNamespace;
定義了這個 class 的命名空間,也就是說當我們之後需要實例化這個 class 的時候,我們不能像這樣子實例化這個 class new MyClass();
,我們需要在 class 名稱前面加上命名空間,命名空間以 \
做分隔,new \MyNamespace\MyClass();
,如同上面的 index.php 一樣,執行 index.php 完美的輸出...
This is my class...
讓我們來依照 PSR-0 的 autoload 範例來寫我們的 autoload 吧!
首先我們想要將我們的 class 全部整理到根目錄下的 classes 目錄的 MyNamespace 目錄底下,我們先來看一下我們的目錄結構。
+ classes
+-+ MyNamespace
- autoload.php
- index.php
當然,我們還沒有建立任何的 class 我們的 MyNmaespace 目錄底下,我們先來看一下 autoload.php 寫了些什麼。
<?php
//autoload.php
function autoload($className)
{
$className = ltrim($className, '\\');
$fileName = '';
$namespace = '';
if ($lastNsPos = strrpos($className, '\\')) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = 'classes/' . str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
require $fileName;
}
spl_autoload_register('autoload');
autoload.php 會依照定義的 namespace 把 \
替換成系統的目錄分隔符號(最後一個 \
之後的 _
也會被替換成系統目錄分隔符號),找到相對應的 class 檔案並引入。我們只要在程式的一開始引入這支 autoload.php 檔案,就可以在不需要手動引入 class 的情況下任意的實例化 MyNamespace 目錄底下的 class 檔案並進行操作,讓我們實際在 MyNamespace 目錄底下建立一個 class 及 兩個子目錄各包含一個 class 檔案。
+ classes
+-+ MyNamespace
+-+-- MyClass.php
+-+-+ Member
+-+-+-- Member.php
+-+-+ Email
+-+-+-- Mailler.php
- autoload.php
- index.php
<?php
//classes/MyNamespace/MyClass.php
namespace MyNamespace;
class MyClass
{
public function doSomething()
{
echo "This is my class...<br>";
}
}
<?php
//classes/MyNamespace/Member/Member.php
namespace MyNamespace\Member;
class Member
{
public function getMemberList()
{
echo "This is member list...<br>";
}
}
<?php
//classes/MyNamespace/Email/Mailler.php
namespace MyNamespace\Email;
class Mailler
{
public function sendMail()
{
echo "send email...<br>";
}
}
值得注意的是當我們在建立一個新的 class 的時候,千萬記得將 class 的 namespace 定義到正確的目錄結構上,這樣子我們的 autoload 才能夠正確的辨認相對應的 class,我們來看看我們的 index.php 內容與執行的結果吧!
<?php
//index.php
include_once __DIR__ . "/autoload.php";
$myClass = new \MyNamespace\MyClass();
$myClass->doSomething();
$member = new \MyNamespace\Member\Member();
$member->getMemberList();
$mailler = new \MyNamespace\Email\Mailler();
$mailler->sendMail();
執行結果如下。
This is my class...
This is member list...
send email...
我們只引入了 autoload.php,之後只要是要實例化在 MyNamespace 目錄底下的 class 就都不用再手動引入了,未來想要增加新的 class 也不需要再去定義 array 或是 設定 php include path,如果覺得 new \MyNamespace\Member\Member();
這樣的宣告方式太冗長了,可以考慮使用 use
這個 keyword,在 Using namespaces: Aliasing/Importing 有詳細的用法。
Okay,我想我們今天該告一段落了,如果覺得不想自己寫這些 autoload 的 function,有機會我會在寫一篇如何使用 composer 套件管理中的 autoload 來更方便的管理你的 autoload,我們下次見^^