一、自動加載定義
很多開發者寫面向對象的應用程序時對每個類的定義建立一個 PHP 源文件。一個很大的煩惱是不得不在每個腳本開頭寫一個長長的包含文件列表(每個類一個文件)。
在 PHP 5 中,不再需要這樣了。可以定義一個 __autoload() 函數,它會在試圖使用尚未被定義的類時自動調用。通過調用此函數,腳本引擎在 PHP 出錯失敗前有了最后一個機會加載所需的類
__autoload 是一個魔術方法, 嘗試加載未定義的類,spl_autoload_register - 注冊給定的函數作為 __autoload 的實現
Tip
spl_autoload_register() 提供了一種更加靈活的方式來實現類的自動加載。因此,不再建議使用 __autoload()函數,在以后的版本中它可能被棄用。
Note:
在 5.3.0 版之前,__autoload 函數拋出的異常不能被 catch 語句塊捕獲並會導致一個致命錯誤。從 5.3.0+ 之后,__autoload 函數拋出的異常可以被 catch 語句塊捕獲,但需要遵循一個條件。如果拋出的是一個自定義異常,那么必須存在相應的自定義異常類。__autoload 函數可以遞歸的自動加載自定義異常類。
Note:
自動加載不可用於 PHP 的 CLI 交互模式。
Note:
如果類名比如被用於 call_user_func(),則它可能包含一些危險的字符,比如 ../。 建議您在這樣的函數中不要使用用戶的輸入,起碼需要在 __autoload() 時驗證下輸入
二、spl_autoload_register
目錄
classes --+
+ mail.class.php
+ norman.class.php
+ db.class.php
1、Single Autoloads
<?php
/*** nullify any existing autoloads ***/
spl_autoload_register(null, false);
/*** specify extensions that may be loaded ***/
spl_autoload_extensions('.php, .class.php');
/*** class Loader ***/
function classLoader($class)
{
$filename = strtolower($class) . '.class.php';
$file ='classes/' . $filename;
if (!file_exists($file))
{
return false;
}
include $file;
}
/*** register the loader functions ***/
spl_autoload_register('classLoader');
/*** a new instance if norman ***/
$norman = new norman;
/*** make norman do something ***/
$norman->do_something();
?>
2、Mulitple Autoloads
<?php
/*** nullify any existing autoloads ***/
spl_autoload_register(null, false);
/*** specify extensions that may be loaded ***/
spl_autoload_extensions('.php, .class.php, .lib.php');
/*** class Loader ***/
function classLoader($class)
{
$filename = strtolower($class) . '.class.php';
$file ='classes/' . $filename;
if (!file_exists($file))
{
return false;
}
include $file;
}
function libLoader($class)
{
$filename = strtolower($class) . '.lib.php';
$file ='libs/' . $filename;
if (!file_exists($file))
{
return false;
}
include $file;
}
/*** register the loader functions ***/
spl_autoload_register('classLoader');
spl_autoload_register('libLoader');
/*** a new instance of norman ***/
$norman = new norman;
/*** make norman do some thing ***/
$norman->do_something();
3、Interfaces
接口文件
<?php
/*
* icontroller.class.php
* interface to ensure all classes have an index method
*
*/
interface iController
{
public function index();
}
?>
autoload文件
<?php
/*** nullify any existing autoloads ***/
spl_autoload_register(null, false);
/*** specify extensions that may be loaded ***/
spl_autoload_extensions('.php, .class.php');
/*** class Loader ***/
function classLoader($class)
{
$filename = strtolower($class) . '.class.php';
$file ='classes/' . $filename;
if (!file_exists($file))
{
return false;
}
include $file;
}
/*** register the loader functions ***/
spl_autoload_register('classLoader');
class blog implements iController
{
public function index()
{
echo 'hello from the index';
}
}
/*** a new blog instance ***/
$blog = new blog;
/*** run the index method ***/
$blog->index();
?>
4、一個標准的示例
spl_autoload_register( 'autoload' );
/**
* autoload
*
* @author testdd
* @param string $class
* @param string $dir
* @return bool
*/
function autoload( $class, $dir = null ) {
if ( is_null( $dir ) )
$dir = '/path/to/project';
foreach ( scandir( $dir ) as $file ) {
// directory?
if ( is_dir( $dir.$file ) && substr( $file, 0, 1 ) !== '.' )
autoload( $class, $dir.$file.'/' );
// php file?
if ( substr( $file, 0, 2 ) !== '._' && preg_match( "/.php$/i" , $file ) ) {
// filename matches class?
if ( str_replace( '.php', '', $file ) == $class || str_replace( '.class.php', '', $file ) == $class ) {
include $dir . $file;
}
}
}
}
5、框架中的寫法
<?php
/**
* Autoloader
* @author Jianxiang Qin <TalkativeDoggy@gmail.com>
* @license http://opensource.org/licenses/BSD-3-Clause New BSD License
* @version svn:$Id$
* @package Lotusphp\Autoloader
*/
/**
* 自動加載類和函數
*
* 按需加載類,每次只加載用到的類。
*
* 函數庫文件不是按需加載!若支持加載函數,則所有定義函數的文件都會加載。
*
* 代碼中用到一個類或者函數的時候,不需要使用include/require來包含類庫文件或者函數庫文件。
*
* 基於Autoloader組件的代碼中將不用使用include/require。
*
* Autoloader緩存的是絕對路徑,能讓Opcode Cache有效緩存文件。
*
* Autoloader要求類的名字唯一,不在意類文件的路徑和文件名。目前不支持命名空間(PHP5.3)
*
* 傳統的include/require通常存在以下問題。
* <ul>
* <li>目錄名和文件名變化引起程序代碼變化。</li>
* <li>Windows和Linux對文件路徑的大小寫和目錄分隔符號的處理不同導致代碼在不同平台遷移時出現問題。</li>
* <li>include_path相對路徑的性能低(顯著地低)。</li>
* <li>為了保證不重復包含,使用include_once和require_once導致效率低(不是顯著的低)。</li>
* </ul>
* @author Jianxiang Qin <TalkativeDoggy@gmail.com> Yi Zhao <zhao5908@gmail.com>
* @category runtime
* @package Lotusphp\Autoloader
* @todo 所有class-file mapping當成一個數據寫入storeHandle
*/
class LtAutoloader
{
/**
* @var bool true|false 是否自動加載定義了函數的文件。
* false 只自動加載定義了class或者interface的文件。
* true (默認) 自動加載定義了函數的文件。
*/
public $isLoadFunction = true;
/**
* @var array 要掃描的文件類型
* 若該屬性設置為array("php","inc","php3"),
* 則擴展名為"php","inc","php3"的文件會被掃描,
* 其它擴展名的文件會被忽略
*/
public $allowFileExtension = array('php', 'inc');
/**
* @var array 不掃描的目錄
* 若該屬性設置為array(".svn", ".setting"),
* 則所有名為".setting"的目錄也會被忽略
*/
public $skipDirNames = array('.svn', '.git');
/** @var LtStoreFile 存儲句柄默認使用 @link LtStoreFile */
public $storeHandle;
/** @var array 指定需要自動加載的目錄列表 */
public $autoloadPath;
/** @var bool
* true 開發模式下 每次都會掃描目錄列表
* false 生產環境下 只掃描一次
*/
public $devMode = true;
/** @var array 函數名 -> 文件路徑 映射 */
private $functionFileMapping = array();
/** @var array 類名 -> 文件路徑 映射 */
private $classFileMapping = array();
/** @var array 定義了函數的文件列表 */
private $functionFiles = array();
/** @var LtStoreFile 持久存儲句柄,存儲文件的get_token_all分析結果/filesize/filehash @link LtStoreFile */
private $persistentStoreHandle;
/** @var int store name space id */
private $storeNameSpaceId;
/** @var int number of parse error */
private $parseErrorAmount = 0;
/** @var int number of library files successfully parsed */
private $libFileAmount = 0;
/**
* 遞歸掃描指定的目錄列表,根據@see LtAutoloader::$isLoadFunction是否加載全部的函數定義文件。
* 注冊自動加載函數,按需加載類文件。
* @return void
*/
public function init()
{
$this->storeNameSpaceId = sprintf("%u", crc32(serialize($this->autoloadPath)));
if (true != $this->devMode)
{
if ($this->storeHandle instanceof LtStore)
{
$this->storeHandle->prefix = 'Lt-Autoloader-' . $this->storeNameSpaceId;
}
else
{
if (null == $this->storeHandle)
{
$this->storeHandle = new LtStoreFile;
$this->storeHandle->prefix = 'Lt-Autoloader-' . $this->storeNameSpaceId;
$this->storeHandle->useSerialize = true;
$this->storeHandle->init();
}
else
{
trigger_error("You passed a value to autoloader::storeHandle, but it is NOT an instance of LtStore");
}
}
}
else
{
$this->storeHandle = new LtStoreMemory;
}
// Whether scanning directory
if ($storedMap = $this->storeHandle->get("map"))
{
$this->classFileMapping = $storedMap["classes"];
$this->functionFiles = $storedMap["functions"];
}
else
{
$this->setPersistentStoreHandle();
$autoloadPath = $this->preparePath($this->autoloadPath);
foreach($autoloadPath as $path)
{
if (is_file($path))
{
$this->addFileMap($path);
}
}
$this->scanDirs($autoloadPath);
unset($autoloadPath);
}
// Whether loading function files
$this->loadFunctionFiles();
spl_autoload_register(array($this, "loadClass"));
}
protected function setPersistentStoreHandle()
{
$this->persistentStoreHandle = new LtStoreFile;
$this->persistentStoreHandle->prefix = 'Lt-parsed-token-' . $this->storeNameSpaceId;
$this->persistentStoreHandle->useSerialize = true;
}
/**
* Autoloader掃描項目,若某個php文件中定義了函數,則此文件的絕對路徑被緩存,
* 每次執行LtAutoloader->init()方法時,自動include所有定義了函數的php文件。
* 因為PHP的Autoload機制是針對Class的.function文件沒有辦法按需加載
* @return void
*/
protected function loadFunctionFiles()
{
if ($this->isLoadFunction && count($this->functionFiles))
{
foreach ($this->functionFiles as $functionFile)
{
include_once($functionFile);
}
}
}
/**
* 被注冊的自動加載函數
* @param string $className
* @return void
*/
protected function loadClass($className)
{
if ($filePath = $this->getFilePathByClassName($className))
{
include($filePath);
}
}
/**
* 將目錄分隔符號統一成linux目錄分隔符號/
* @param string $path
* @return boolean
*/
protected function convertPath($path)
{
$path = str_replace("\\", "/", $path);
if (!is_readable($path))
{
trigger_error("Directory is not exists/readable: {$path}");
return false;
}
$path = rtrim(realpath($path), '\\/');
if (preg_match("/\s/i", $path))
{
trigger_error("Directory contains space/tab/newline is not supported: {$path}");
return false;
}
return $path;
}
/**
* The string or an Multidimensional array into a one-dimensional array
* 將字符串和多維數組轉換成一維數組
* @param mixed $paths
* @return array one-dimensional array
*/
protected function preparePath($paths)
{
$oneDPathArray = array();
if (!is_array($paths))
{
$paths = array($paths);
}
$i = 0;
while (isset($paths[$i]))
{
if (!is_array($paths[$i]) && $path = $this->convertPath($paths[$i]))
{
$oneDPathArray[] = $path;
}
else
{
foreach($paths[$i] as $v)
{
$paths[] = $v;
}
}
$i ++;
}
unset($paths);
return $oneDPathArray;
}
/**
* Using iterative algorithm scanning subdirectories
* save autoloader filemap
* 遞歸掃描目錄包含子目錄,保存自動加載的文件地圖。
* @param array $dirs one-dimensional
* @return void
* @todo in_array換成array_key_exists以提升性能
*/
protected function scanDirs($dirs)
{
$i = 0;
while (isset($dirs[$i]))
{
$dir = $dirs[$i];
$files = scandir($dir);
foreach ($files as $file)
{
$currentFile = $dir . DIRECTORY_SEPARATOR . $file;
if (is_file($currentFile))
{
$this->addFileMap($currentFile);
}
else if (is_dir($currentFile))
{
if (in_array($file, array(".", "..")) || in_array($file, $this->skipDirNames))
{
continue;
}
else
{
// if $currentFile is a directory, pass through the next loop.
$dirs[] = $currentFile;
}
}
else
{
trigger_error("$currentFile is not a file or a directory.");
}
} //end foreach
$i ++;
} //end while
if(0 == $this->parseErrorAmount)
{
$this->functionFiles = array_unique(array_values($this->functionFileMapping));
$map = array("classes" => $this->classFileMapping, "functions" => $this->functionFiles);
if ($this->storeHandle->get("map"))
{
$this->storeHandle->update("map", $map);
}
else
{
$this->storeHandle->add("map", $map);
}
}
else
{
trigger_error($this->parseErrorAmount . " error(s) occoured when scanning and parsing your lib files");
}
}
/**
* 分析出字符串中的類,接口,函數。
* @param string $src
* @return array
* @todo 若當前文件包含了直接執行的php語句,或者html,輸出警告
* @todo 若當前文件有語法錯誤,拋出異常
*/
protected function parseLibNames($src)
{
$libNames = array();
$tokens = token_get_all($src);
$level = 0;
$found = false;
$name = '';
foreach ($tokens as $token)
{
if (is_string($token))
{
if ('{' == $token)
{
$level ++;
}
else if ('}' == $token)
{
$level --;
}
}
else
{
list($id, $text) = $token;
if (T_CURLY_OPEN == $id || T_DOLLAR_OPEN_CURLY_BRACES == $id)
{
$level ++;
}
if (0 < $level)
{
continue;
}
switch ($id)
{
case T_STRING:
if ($found)
{
$libNames[strtolower($name)][] = $text;
$found = false;
}
break;
case T_CLASS:
case T_INTERFACE:
case T_FUNCTION:
$found = true;
$name = $text;
break;
}
}
}
return $libNames;
}
/**
* 保存類名、接口名和對應的文件絕對路徑。
* @param string $className
* @param string $file
* @return boolean
*/
protected function addClass($className, $file)
{
$key = strtolower($className);
if (isset($this->classFileMapping[$key]))
{
$existedClassFile = $this->classFileMapping[$key];
trigger_error("duplicate class [$className] found in:\n$existedClassFile\n$file\n, or please clear the cache");
return false;
}
else
{
$this->classFileMapping[$key] = $file;
return true;
}
}
/**
* 保存函數名和對應的文件絕對路徑
* @param string $functionName
* @param string $file
* @return boolean
*/
protected function addFunction($functionName, $file)
{
$functionName = strtolower($functionName);
if (isset($this->functionFileMapping[$functionName]))
{
$existedFunctionFile = $this->functionFileMapping[$functionName];
trigger_error("duplicate function [$functionName] found in:\n$existedFunctionFile\n$file\n");
return false;
}
else
{
$this->functionFileMapping[$functionName] = $file;
return true;
}
}
/**
* 將文件添加到自動加載的FileMap,
* 添加之前會判斷自從上次掃描后有沒有修改,若沒有修改則無需重復添加,
* 若修改過,則分析文件內容,根據內容中包含的類、接口,函數添加到FileMap
* @param string $filePath
* @return boolean
*/
protected function addFileMap($filePath)
{
if (!in_array(pathinfo($filePath, PATHINFO_EXTENSION), $this->allowFileExtension))
{//init()會調用這個方法, 不要將這個判斷移動到scanDir()中
return false;
}
$fileSize = filesize($filePath);
$fileHash = md5_file($filePath);
$savedFileInfo = $this->persistentStoreHandle->get($filePath);
if (!isset($savedFileInfo['file_size']) || $savedFileInfo['file_size'] != $fileSize || $savedFileInfo['file_hash'] != $fileHash)
{
if($libNames = $this->parseLibNames(trim(file_get_contents($filePath))))
{
$newFileInfo = array('file_size' => $fileSize, 'file_hash' => $fileHash, 'lib_names' => $libNames);
if (isset($savedFileInfo['file_size']))
{
$this->persistentStoreHandle->update($filePath, $newFileInfo);
}
else
{
$this->persistentStoreHandle->add($filePath, $newFileInfo);
}
}
else
{
$this->parseErrorAmount ++;
}
}
else
{
$libNames = $savedFileInfo['lib_names'];
}
foreach ($libNames as $libType => $libArray)
{
$method = "function" == $libType ? "addFunction" : "addClass";
foreach ($libArray as $libName)
{
if (!$this->$method($libName, $filePath))
{
$this->parseErrorAmount ++;
}
}
}
return true;
}
protected function getFilePathByClassName($className)
{
$key = strtolower($className);
if (isset($this->classFileMapping[$key]))
{
return $this->classFileMapping[$key];
}
else
{
return false;
}
}
}
6、set_include_path 方式
set_include_path(implode(PATH_SEPARATOR, array(get_include_path(), './services', './printers'))); spl_autoload_register();
7、PSR-4: Autoloader
http://www.php-fig.org/psr/psr-4/
參考文章
http://www.phpro.org/tutorials/SPL-Autoload.html
https://github.com/qinjx/adv_php_book/blob/master/class_autoload.md
http://php.net/manual/zh/language.oop5.autoload.php
