13

我一直在看 php 的 autoload() 函数。似乎是个好主意,但我不确定它如何处理多个目录。我目前的开发基本上有一个库目录结构,通过操作将类分组到子目录中。我想知道我必须为每个目录声明一个 include() ......我真的希望我不必这样做。

你能建议 - 谢谢

4

6 回答 6

17

您可能想查看类名的PEAR 约定,这对于自动加载非常有用。

基本上,它指出:

PEAR 类层次结构也反映在类名称中,层次结构的每一级用一个下划线分隔。

这意味着找到要包含在类名HTML_Upload_Error中的文件只是将 '_' 替换为 '/' 的问题;给你HTML/Upload/Error.php

有关更多解释和几个示例,您可以查看文章:

顺便说一句:许多大型框架/库都使用此约定;-)
例如,Zend Framework 使用此约定——它真的很有帮助!

于 2009-08-31T19:07:09.163 回答
10

这是我不久前出于类似目的而写的一门课程。那个时候我还在学习阶段,可能会有一些愚蠢的想法;尽管如此,它仍然有效。

基本思想是它扫描一次源目录,并创建一个数组映射类到它们的源文件。该类被注册为一个自动加载器,当被调用时,它包含了所需的文件。如果未找到,它会尝试即时重建阵列。

/* 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;
    }

}
于 2009-08-31T20:01:00.787 回答
1

不幸的是,您必须明确添加每个目录。这可以在递归遍历目录的脚本中以编程方式完成,也可以指定一个列表。

可能最有效的方法是指定要搜索的目录和子目录列表,然后使用 ini_set() 将它们添加到“include_path”中。

于 2009-08-31T19:05:33.127 回答
1

你似乎很困惑 :) 或者我对你的问题感到困惑。

编写一个定位和加载类的函数完全取决于您,PHP 不在乎它在哪里/有多少层。

并且,查看SPL 自动加载,它具有相同的基本功能,但您可以编写多个自动加载函数,然后将它们链接起来。如果您想使用一些外部库,这些库定义了自己的可能与您的自动加载器冲突的自动加载器,这可能很有用。

于 2009-08-31T19:07:59.457 回答
1

我假设您正在谈论 PHP 的 SPL 自动加载功能 - 您在其中编写自己的函数,然后将其注册到 SPL。

你如何做取决于你如何创建你的包含函数。可以声明多个包含函数,然后用 PHP 注册它们:多少由您决定。SPL 自动加载功能只允许您创建自己的函数,然后在每次需要包含类时告诉 PHP 运行该函数。

创建多个的一个好处是能够按使用顺序注册它们,最常用的目录首先到最少使用的最后。此外,如果目录被更改或删除,那么您只需更改和/或删除负责的功能。

您也可以编写一个函数来遍历整个文件夹结构(尽管为了便于管理和代码解耦,我不推荐它)。没有“技术上正确”的方法来做到这一点:)

于 2009-08-31T19:08:00.800 回答
1

如前所述,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')解析为的自动加载器目录映射,然后加载并验证类。

于 2009-08-31T20:27:12.840 回答