为 PHP 编写 Bulletproof __autoload 函数

发布于 2024-09-10 11:28:14 字数 818 浏览 2 评论 0原文

我试图将我的 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 文件夹中,它将正确打印。

关于使其更加灵活的建议?

I'm trying to define my PHP __autoload function to be as bulletproof and flexible as possible.

Here's the breakdown of my application's structure:

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

It's rather straight forward. My problem is this: how do I write my __autoload function so that I can include any class I want regardless of how deeply nested the calling file is within the directory structure. I know it has something to do with the __FILE__ , realpath and dirname functions, but I'm not certain as to the appropriate way to combine them to achieve the flexibility I'm after.

Here's a quick test I did:

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

The result:

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

As you can see, the result doesn't match where the class is located. However, if I moved the myscript.php file into /my_app folder, it would print correctly.

Suggestions on making this more flexible?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

肤浅与狂妄 2024-09-17 11:28:14

我建议查看 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

I would suggest looking into spl_autoload. just add the proper directories to your include_path

Something like this can help probably you get started:

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

You'll have to either provide your own autoloader using spl_autoload_register or lowercase all your filenames.

Here's one of my own autoloaders, which uses php namespaces to overcome some directory issues.

<?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;

It's part of a class named Loader in the \Red namespace and gets initialized from a simple bootstrap file:

<?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
迷乱花海 2024-09-17 11:28:14

$_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/';

$_SERVER['DOCUMENT_ROOT'] should contain the full path to the root directory of your web server. From there you should be able to continue the path through your folder structure to the class directory. If you stored the app name in session, the same code could be used pretty much anywhere.

//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/';
陈独秀 2024-09-17 11:28:14

我不会选择自动文件位置检测。除非仔细考虑它可能是一个安全漏洞,否则您需要设计一个支持子目录的命名示意图,并且它迫使您每个类使用一个文件(这可能是一件好事,也可能是一件坏事,但我发现它不灵活)。我会使用全局或静态数组来保存映射 className =>路径到类。

I would not opt for automatic file location detection. Unless well thought about it can be a security vulnerability, you need to design a naming schematic that supports subdirectories, and it forces you to use one file per class (which can either be a good or bad thing, but I find it inflexible). I would use a global or static array where you keep a mapping className => pathToClass.

逆光下的微笑 2024-09-17 11:28:14

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

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

无需在此处获取 realpath,PHP 可以使用相对路径。 ;)

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

There are basically two ways. Either you specify the full absolute path to your class directory (/home/mydirectory/dev.mysite.com/my_app/php/classes/), which I would not recommend because it involves changing the absolute path if you change hosts. Or you may use a relative path, which is way easier and portable:

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

No need to get the realpath here, PHP is fine with relative paths. ;)

PS: realpath(dirname(__FILE__)) is duplicate I think. __FILE__ is already a "real path" and therefore you don't need to call realpath.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文