我一直在看 php 的 autoload() 函数。似乎是个好主意,但我不确定它如何处理多个目录。我目前的开发基本上有一个库目录结构,通过操作将类分组到子目录中。我想知道我必须为每个目录声明一个 include() ......我真的希望我不必这样做。
你能建议 - 谢谢
您可能想查看类名的PEAR 约定,这对于自动加载非常有用。
基本上,它指出:
PEAR 类层次结构也反映在类名称中,层次结构的每一级用一个下划线分隔。
这意味着找到要包含在类名HTML_Upload_Error
中的文件只是将 '_' 替换为 '/' 的问题;给你HTML/Upload/Error.php
有关更多解释和几个示例,您可以查看文章:
顺便说一句:许多大型框架/库都使用此约定;-)
例如,Zend Framework 使用此约定——它真的很有帮助!
这是我不久前出于类似目的而写的一门课程。那个时候我还在学习阶段,可能会有一些愚蠢的想法;尽管如此,它仍然有效。
基本思想是它扫描一次源目录,并创建一个数组映射类到它们的源文件。该类被注册为一个自动加载器,当被调用时,它包含了所需的文件。如果未找到,它会尝试即时重建阵列。
/* register ClassLoader as class loader */
spl_autoload_register(array(ClassLoader::getInstance(), 'loadClass'));
class ClassLoader {
private static $SAVE_FILE = 'ClassLoader.save.php';
/* singleton */
private static $instance;
/* stores a className -> filePath map */
private $classList;
/* tells whether working from saved file */
private $refreshed;
public static function getInstance() {
if (!isset(self::$instance)) {
self::$instance = new ClassLoader();
}
return self::$instance;
}
private function __construct() {
$this->initClassList();
}
public function loadClass($className) {
if ( !array_key_exists($className, $this->classList) && !$this->refreshed ) {
$this->refreshClassList();
}
require_once($this->classList[$className]);
}
private function initClassList() {
if (file_exists(INCLUDES_DIR . self::$SAVE_FILE)) {
require_once(INCLUDES_DIR . self::$SAVE_FILE);
$this->refreshed = FALSE;
} else {
$this->refreshClassList();
}
}
private function refreshClassList() {
$this->classList = $this->scanDirectory(INCLUDES_DIR);
$this->refreshed = TRUE;
$this->saveClassList();
}
private function saveClassList() {
$handle = fopen(INCLUDES_DIR . self::$SAVE_FILE, 'w');
fwrite($handle, "<?php\r\n");
foreach($this->classList as $class => $path) {
$line = '$this->classList' . "['" . $class . "'] = '" . $path . "';\r\n";
fwrite($handle, $line);
}
fwrite($handle, '?>');
fclose($handle);
}
private function scanDirectory ($directory) {
// strip closing '/'
if (substr($directory, -1) == '/') {
$directory = substr($directory, 0, -1);
}
if (!file_exists($directory) || !is_dir($directory) || !is_readable($directory)) {
return array();
}
$dirH = opendir($directory);
$scanRes = array();
while(($file = readdir($dirH)) !== FALSE) {
// skip pointers
if ( strcmp($file , '.') == 0 || strcmp($file , '..') == 0) {
continue;
}
$path = $directory . '/' . $file;
if (!is_readable($path)) {
continue;
}
// recursion
if (is_dir($path)) {
$scanRes = array_merge($scanRes, $this->scanDirectory($path));
} elseif (is_file($path)) {
$className = explode('.', $file);
if ( strcmp($className[1], 'class') == 0 && strcmp($className[2], 'php') == 0 ) {
$scanRes[$className[0]] = $path;
}
}
}
return $scanRes;
}
}
不幸的是,您必须明确添加每个目录。这可以在递归遍历目录的脚本中以编程方式完成,也可以指定一个列表。
可能最有效的方法是指定要搜索的目录和子目录列表,然后使用 ini_set() 将它们添加到“include_path”中。
你似乎很困惑 :) 或者我对你的问题感到困惑。
编写一个定位和加载类的函数完全取决于您,PHP 不在乎它在哪里/有多少层。
并且,查看SPL 自动加载,它具有相同的基本功能,但您可以编写多个自动加载函数,然后将它们链接起来。如果您想使用一些外部库,这些库定义了自己的可能与您的自动加载器冲突的自动加载器,这可能很有用。
我假设您正在谈论 PHP 的 SPL 自动加载功能 - 您在其中编写自己的函数,然后将其注册到 SPL。
你如何做取决于你如何创建你的包含函数。可以声明多个包含函数,然后用 PHP 注册它们:多少由您决定。SPL 自动加载功能只允许您创建自己的函数,然后在每次需要包含类时告诉 PHP 运行该函数。
创建多个的一个好处是能够按使用顺序注册它们,最常用的目录首先到最少使用的最后。此外,如果目录被更改或删除,那么您只需更改和/或删除负责的功能。
您也可以编写一个函数来遍历整个文件夹结构(尽管为了便于管理和代码解耦,我不推荐它)。没有“技术上正确”的方法来做到这一点:)
如前所述,SPL 自动加载在功能上是一种结构,您必须将实际实现移植到该结构上——目录遍历和命名约定是这些考虑的一部分。
以 Zend Loader 的形式举一个实际的例子:在它的基础上,这是一个单例,它使用将命名空间与使用 PHP 的包含路径注册的目录相关联的约定。实际例子:
set_include_path(get_include_path(). PATH_SEPARATOR. 'App/'); //Concat the "App" directory onto the existing include paths
$loader = Zend_Loader::getInstance(); //because the autoloader is a singleton, we get a reference to it without assuming we need to first create it
$loader->registerNamespace('App_'); //Tell autoloader it can look in the app directory to find classes if it can't find them in the default Zend directory.
显然,具体的实现问题会因项目而异,但作为理解练习和代码重用的练习,最好尝试编写一个可以将特定类格式(例如'directory_classname')解析为的自动加载器目录映射,然后加载并验证类。