【问题标题】:Writing a Bulletproof __autoload Function for PHP为 PHP 编写一个防弹的 __autoload 函数
【发布时间】:2010-07-17 20:45:03
【问题描述】:

我正在尝试将我的 PHP __autoload 函数定义为尽可能防弹和灵活。

这是我的应用程序结构的细分:

/dev (root)
    /my_app
        /php
            /classes
                - Class1.php
                - Class2.php
                - Class3.php
        /scripts
            myscript.php (the file I have to include the classes in)

这是相当直截了当的。我的问题是:我如何编写我的 __autoload 函数,以便我可以包含我想要的任何类,而不管调用文件在目录结构中的嵌套有多深。我知道这与 __FILE__realpathdirname 函数有关,但我不确定将它们组合起来以实现我所追求的灵活性的适当方法。

这是我做的一个快速测试:

<?php
echo realpath(dirname(__FILE__)) . "/php/classes/Class1.php";
?>

结果:

/home/mydirectory/dev.mysite.com/my_app/php/scripts/php/classes/Class1.php

如您所见,结果与类所在的位置不匹配。但是,如果我将 myscript.php 文件移动到 /my_app 文件夹中,它将正确打印。

关于使其更灵活的建议?

【问题讨论】:

标签: php autoload


【解决方案1】:

我建议调查spl_autoload。只需将正确的目录添加到您的 include_path

这样的事情可能会帮助您入门:

ini_set($your_class_dir_here .PATH_SEPERATOR. ini_get('include_path'));

您必须使用spl_autoload_register 提供您自己的自动加载器,或者将所有文件名小写。

这是我自己的一个自动加载器,它使用 php 命名空间来解决一些目录问题。

<?php

namespace Red
{
    // since we don't have the Object yet as we load this file, this is the only place where this needs to be done.
    require_once 'Object.php';

    /**
     * Loader implements a rudimentary autoloader stack.
     */
    class Loader extends Object
    {
        /**
         * @var Loader 
         */
        static protected $instance = null;

        /**
         * @var string 
         */
        protected $basePath;

        /**
         * @return Loader
         */
        static public function instance()
        {
            if (self::$instance == null)
            {
                self::$instance = new self();
            }
            return self::$instance;
        }

        /**
         * Initialize the autoloader. Future expansions to the 
         * autoloader stack should be registered in here.
         */
        static public function Init()
        {
            spl_autoload_register(array(self::instance(), 'autoLoadInNamespace'));
        }

        /**
         * PHP calls this method when a class is used that has not been
         * defined yet. 
         * 
         * I'm returning a boolean for success which isn't required (php ignores it)
         * but makes life easier when the stack grows.
         * 
         * @param string $fullyQualifiedClassName
         * @return boolean 
         */
        public function autoLoadInNamespace($fullyQualifiedClassName)
        {
            $pathParts = preg_split('/\\\\/', $fullyQualifiedClassName, -1, PREG_SPLIT_NO_EMPTY);
            array_unshift($pathParts, $this->basePath);
            $pathToFile = implode(DIRECTORY_SEPARATOR, $pathParts) . '.php';

            if (file_exists($pathToFile))
            {
                require_once $pathToFile;
                return true;
            }
            return false;
        }

        /**
         * Constructor is protected because we don't want multiple instances
         * But we do want an instance to talk to later on.
         */
        protected function __construct()
        {
            $this->basePath = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..');
        }
    }
}

#EOF;

它是 \Red 命名空间中名为 Loader 的类的一部分,并从一个简单的引导文件初始化:

<?php
// This is where the magic is prepared. 
require_once implode(DIRECTORY_SEPARATOR, array(dirname(__FILE__), 'Red', 'Loader.php'));
// Initialize the autoloader, no more require_once for every class
// and everything is OOP from here on in.
Red\Loader::Init();

#EOF

【讨论】:

    【解决方案2】:

    $_SERVER['DOCUMENT_ROOT'] 应该包含您的 Web 服务器根目录的完整路径。从那里您应该能够继续通过文件夹结构到类目录的路径。如果您将应用名称存储在会话中,则几乎可以在任何地方使用相同的代码。

    //set in config file
    if(!isset($_SESSION['APP_DIR'])) $_SESSION['APP_DIR'] = "my_app";
    
    //autoload
    //builds a string with document root/app_name/classes
    //takes the app name and replaces anything not alphanumeric or _ with an _ and
    //makes it lowercase in case of case sensitive. as long as you follow this naming
    //scheme for app directories it should be fine for multiple apps.
    $classPath = $_SERVER['DOCUMENT_ROOT'] . '/' .
               strtolower(preg_replace('/\W+/', '_', $_SESSION['APP_DIR'])) .
               '/classes/';
    

    【讨论】:

    • 我正在尝试使用相对路径。
    【解决方案3】:

    我不会选择自动文件位置检测。除非经过深思熟虑它可能是一个安全漏洞,否则您需要设计一个支持子目录的命名示意图,并且它会强制您每个类使用一个文件(这可能是好事也可能是坏事,但我觉得它不灵活)。我会使用一个全局或静态数组,在其中保留一个映射 className => pathToClass。

    【讨论】:

    • 这听起来像是一场维护噩梦。除非您使用构建过程来创建该数组。
    • 但是这个数组是我每次创建新类时都必须维护的另一件事;这就是我要避免的问题。我不想让数组保持最新或将另一个要求语句复制并粘贴到引用新类的所有文件中
    【解决方案4】:

    基本上有两种方法。您可以指定类目录的完整绝对路径 (/home/mydirectory/dev.mysite.com/my_app/php/classes/),我不建议这样做,因为如果您更改主机,它会涉及更改绝对路径。或者你可以使用相对路径,这样更容易移植:

    require_once '../classes/'.$classname;
    

    这里不需要realpath,PHP 可以使用相对路径。 ;)

    PS:realpath(dirname(__FILE__)) 我认为是重复的。 __FILE__ 已经是“真实路径”,因此您无需调用 realpath

    【讨论】:

    • @nikic,感谢您的回复。但是,如果我们使用 __autoload,这将不起作用,因为目标只是编写一个 require 语句来包含包含 __autoload 函数的文件。我的问题是如何检测到类文件夹的相对路径,而不管从哪里调用 __autoload。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 2020-02-04
    • 2021-01-27
    • 1970-01-01
    相关资源
    最近更新 更多