__autoload 的最佳解决方案

发布于 2024-07-10 08:07:00 字数 618 浏览 4 评论 0原文

随着 PHP5 OO 应用程序的增长(大小和流量),我们决定重新审视 __autoload() 策略。

我们总是通过文件包含的类定义来命名该文件,因此类 Customer 将包含在 Customer.php 中。 我们过去常常列出文件可能存在的目录,直到找到正确的 .php 文件。

这是非常低效的,因为您可能会遍历许多不需要的目录,并且对每个请求都这样做(因此,进行大量 stat() 调用)。

我想到的解决方案...

- 使用指定目录名称的命名约定(类似于 PEAR)。 缺点:扩展性不太好,导致类名很糟糕。

- 提出某种预先构建的位置数组(propel 为其 __autoload 执行此操作)。 缺点:在部署新代码之前需要重建。

-“即时”构建数组并缓存它。 这似乎是最好的解决方案,因为它允许您想要的任何类名和目录结构,并且完全灵活,新文件只需添加到列表中。 问题是:将其存储在哪里以及删除/移动的文件如何。 对于存储,我们选择 APC,因为它没有磁盘 I/O 开销。 至于文件删除,这并不重要,因为您可能不想在任何地方都需要它们。 至于移动......这是尚未解决的(我们忽略它,因为历史上它对我们来说并不经常发生)。

还有其他解决方案吗?

As our PHP5 OO application grew (in both size and traffic), we decided to revisit the __autoload() strategy.

We always name the file by the class definition it contains, so class Customer would be contained within Customer.php. We used to list the directories in which a file can potentially exist, until the right .php file was found.

This is quite inefficient, because you're potentially going through a number of directories which you don't need to, and doing so on every request (thus, making loads of stat() calls).

Solutions that come to my mind...

-use a naming convention that dictates the directory name (similar to PEAR). Disadvantages: doesn't scale too great, resulting in horrible class names.

-come up with some kind of pre-built array of the locations (propel does this for its __autoload). Disadvantage: requires a rebuild before any deploy of new code.

-build the array "on the fly" and cache it. This seems to be the best solution, as it allows for any class names and directory structure you want, and is fully flexible in that new files just get added to the list. The concerns are: where to store it and what about deleted/moved files. For storage we chose APC, as it doesn't have the disk I/O overhead. With regards to file deletes, it doesn't matter, as you probably don't wanna require them anywhere anyway. As to moves... that's unresolved (we ignore it as historically it didn't happen very often for us).

Any other solutions?

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

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

发布评论

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

评论(11

我不吻晚风 2024-07-17 08:07:00

我也已经玩了相当长一段时间的自动加载,并且我最终实现了某种命名空间自动加载器(是的,它也适用于 PHP5.2)。

该策略非常简单:
首先,我有一个单例类(加载器),它有一个模拟导入的调用。 此调用采用一个参数(要加载的完整类名)并在内部计算调用它的文件名(使用 debug_backtrace())。 该调用将此信息存储在关联数组中以便稍后使用(使用调用文件作为键,以及每个键的导入类的列表)。

典型的代码如下所示:

<?php

    loader::import('foo::bar::SomeClass');
    loader::import('foo::bar::OtherClass');

    $sc = new SomeClass();

?>

当自动加载被触发时,存储在数组中的完整类名将转换为真实的文件系统位置(双冒号被目录分隔符替换),并且包含生成的文件名。

我知道这并不完全是您所要求的,但它可以解决目录遍历问题,因为加载程序直接知道文件的确切位置(添加的功能是您可以将类组织在目录中,而没有明显的性能惩罚)。

我可以为您提供一些工作示例,但我太害羞了,无法向公众展示我的蹩脚代码。 希望以上解释有用...

I've also been playing with autoload for quite some time, and I ended up implementing some sort of namespaced autoloader (yes, It works also for PHP5.2).

The strategy is quite simple:
First I have a singleton class (loader) which has a call that simulates import. This call takes one parameter (the full class name to load) and internally calculates the file name it was called from (using debug_backtrace()). The call stores this information in an associative array to use it later (using the calling file as the key, and a list of imported classes for each key).

Typical code looks like this:

<?php

    loader::import('foo::bar::SomeClass');
    loader::import('foo::bar::OtherClass');

    $sc = new SomeClass();

?>

When autoload is fired, the full class name that was stored in the array gets transformed to a real filesystem location (double colons are replaced with directory separators), and the resulting filename is included.

I know that It's not exactly what you were asking for, but it may solve the directory traversal issue, as the loader directly knows where the file exactly is (with the added feature that you could have your classes organized in directories, with no apparent performance penalty).

I could provide you some working examples, but I'm too shy to show my crappy code to the public. Hope the above explanation was useful...

怼怹恏 2024-07-17 08:07:00

有两种行之有效的通用方法。
首先是使用 PEAR 标准类命名结构,因此您只需将 '_' 替换为 / 即可找到该类。

http://pear.php.net/manual/en/pear2cs.rules。 php

或者您可以搜索 php 类的目录并将类名映射到文件。 您可以将类映射保存到缓存,以在每次页面加载时保存搜索目录。
Symfony 框架使用了这些方法。

一般来说,最好遵循标准结构,因为它更简单,并且您不需要缓存任何内容,而且您可以遵循推荐的准则。

There is 2 general approaches that work well.
First is using PEAR standard class naming structure so you just need to replace '_' with / to find the class.

http://pear.php.net/manual/en/pear2cs.rules.php

Or you can search directories for php classes and map class name to file. You can save the class map to cache to save searching directories every page load.
Symfony framework uses those approach.

Generally its better to follow the standard structure as its simpler and you don't need to cache anything, plus you are then following recommended guidelines.

旧人 2024-07-17 08:07:00

我们使用与最后一个选项非常相似的东西,除了在 r​​equire 之前进行 file_exists() 检查。 如果不存在,请重建缓存并重试。 您可以获得每个文件的额外统计信息,但它可以透明地处理移动。 对于我经常移动或重命名事物的快速开发来说非常方便。

We use something much like the last option, except with a file_exists() check before the require. If it doesn't exist, rebuild the cache and try once more. You get the extra stat per file, but it handles moves transparently. Very handy for rapid development where I move or rename things frequently.

安人多梦 2024-07-17 08:07:00

我过去使用过这个解决方案,我在博客上介绍了它以供参考,并且可能对你们中的一些人感兴趣......

这里

I have used this solution in the past, I blogged about it for reference and may be interesting to some of you...

Here it is

尽揽少女心 2024-07-17 08:07:00

CodeIgniter 与 load_class 函数执行类似的操作。 如果我没记错的话,它是一个保存对象数组的静态函数。 对函数的调用是:


 load_class($class_name, $instansiate);

所以在您的情况下


 load_class('Customer', TRUE);

,这会将 Customer 类的实例加载到对象数组中。

该功能非常简单。 抱歉,我不记得它所在的类的名称。但我记得有几个类被加载,例如路由类、基准类和 URI 类。

CodeIgniter does something similar with the load_class function. If I recall correctly, it is a static function that holds an array of objects. The call to the function is:


 load_class($class_name, $instansiate);

so in your case


 load_class('Customer', TRUE);

and this would load an instance of the Customer class into the objects array.

The function was pretty straight forward. Sorry I cant remember the name of the class it was in. But I do recall there being several classes that get loaded such as I think the Routing class, the Benchmark class and the URI class.

栖竹 2024-07-17 08:07:00

如果您使用 APC,则应该启用操作码缓存。 我相信,与您采用的任何类/文件策略相比,这对性能有更大的好处,并且是一个更透明的解决方案。

第二个建议是使用最容易开发的类/文件策略,但在您的生产站点上,您应该将最常用的类连接到一个文件中,并确保在每个请求期间加载该文件(或使用 APC 缓存)。

通过增加课程组数来进行一些性能实验。 您可能会发现减少文件 I/O 的好处是如此显着,即使您在每个请求期间不需要所有类,将所有类一起填充到一个文件中也是一种净胜。

If you're using APC, you should enable opcode caching. I believe this would have more benefit to performance, and be a more transparent solution, than any class/file strategy you employ.

The second suggestion is to use whatever class/file strategy is easiest to develop against, but on your production site, you should concatenate the classes used most frequently into one file and make sure that's loaded during every request (or cached with APC).

Do some performance experiments with increasing sets of your classes. You will probably find that the benefit of reducing file I/O is so significant, that stuffing all the classes together into one file is a net win, even if you don't need all the classes during every request.

懷念過去 2024-07-17 08:07:00

我对类的每种“类型”(控制器、模型、库文件等......)都有特定的命名约定,所以目前我做了类似的事情:

function __autoload($class){
    if($class matches pattern_1 and file_exists($class.pattern_1)){
        //include the file somehow
    } elseif($class matches pattern_2 and file_exists($class.pattern_2)){
        //include the file somehow
    } elseif(file_exists($class.pattern_3)){
        //include the file somehow
    } else {
       //throw an error because that class does not exist?
    }
}

I have specific naming conventions for each 'type' of class (controllers, models, library files, and so on...), so currently I do something similar to:

function __autoload($class){
    if($class matches pattern_1 and file_exists($class.pattern_1)){
        //include the file somehow
    } elseif($class matches pattern_2 and file_exists($class.pattern_2)){
        //include the file somehow
    } elseif(file_exists($class.pattern_3)){
        //include the file somehow
    } else {
       //throw an error because that class does not exist?
    }
}
南烟 2024-07-17 08:07:00

老线程,但我想我无论如何都可以在这里公开我的方法,也许它可以帮助某人。 这是我在网站入口点 /path/to/root/www/index.php 中定义 __autoload() 的方式,例如:

function __autoload($call) {
    require('../php/'.implode('/', explode('___', $call)).'.php');
}

所有 PHP 文件都组织在树

/path/to/root/php
  /Applications
    /Website
      Server.php
  /Model
    User.php
  /Libraries
    /HTTP
      Client.php
    Socket.php

和类名称是:

Applications___Website___Server
Model___User
Libraries___HTTP___Client
Libraries___Socket

它速度很快,如果文件不存在,那么它将崩溃,并且您的错误日志会告诉您缺少哪个文件。 这可能看起来有点苛刻,但如果你尝试使用错误的类,那就是你的问题了。

注意:它适用于 PHP 5 < 5.3,所以对于 PHP 5.3 你可以使用命名空间,我使用 3 _ 作为分隔符的原因是它很容易替代 5.3 命名空间的使用

Old thread but I thought I could expose my method here anyway, maybe it could help someone. This is the way I define __autoload() in my website entry point /path/to/root/www/index.php for example :

function __autoload($call) {
    require('../php/'.implode('/', explode('___', $call)).'.php');
}

All PHP files are in organized in a tree

/path/to/root/php
  /Applications
    /Website
      Server.php
  /Model
    User.php
  /Libraries
    /HTTP
      Client.php
    Socket.php

And classes name are :

Applications___Website___Server
Model___User
Libraries___HTTP___Client
Libraries___Socket

It is fast and if the file is not present, then it will crash and your error log will tell you which file is missing. It may seem a bit harsh but if you try to use the wrong class, it is your problem.

NB : it was for PHP 5 < 5.3, so for PHP 5.3 you may use namespaces, the reason why I used 3 _ as separator is that it is an easy replacement to do for 5.3 namespace use

遇到 2024-07-17 08:07:00
function __autoload($class_name) {
   $class_name = strtolower($class_name);
   $path       = "../includes/{$class_name}.php";
   if (file_exists($path)) {
       require_once($path);
   } else {
       die("The file {$class_name}.php could not be found!");
   }
}
function __autoload($class_name) {
   $class_name = strtolower($class_name);
   $path       = "../includes/{$class_name}.php";
   if (file_exists($path)) {
       require_once($path);
   } else {
       die("The file {$class_name}.php could not be found!");
   }
}
带上头具痛哭 2024-07-17 08:07:00

您是否研究过使用 Zend_Loader (使用 registerAutoload()) 而不是原生的 __autoload ()? 我使用过 Zend_Loader 并且对它很满意,但还没有从性能的角度来看待它。 然而,这篇博文似乎已经完成了对其进行一些性能分析; 您可以将这些结果与当前自动加载机的内部性能测试进行比较,看看它是否可以满足您的期望。

Have you investigated using Zend_Loader (with registerAutoload()) instead of just the native __autoload()? I've used Zend_Loader and have been happy with it, but haven't looked at it from a performance perspective. However, this blog post seems to have done some performance analysis on it; you could compare those results to your in-house performance testing on your current autoloader to see if it can meet your expectations.

从此见与不见 2024-07-17 08:07:00
function __autoload( $class )
{
    $patterns = array( '%s.class.php', '%s.interface.php' );

    foreach( explode( ';', ini_get( 'include_path' ) ) as $dir )
    {
        foreach( $patterns as $pattern )
        {
            $file    = sprintf( $pattern, $class );
            $command = sprintf( 'find -L %s -name "%s" -print', $dir, $file );
            $output  = array();
            $result  = -1;

            exec( $command, $output, $result );

            if ( count( $output ) == 1 )
            {
                require_once( $output[ 0 ] );
                return;
            }
        }
    }

    if ( is_integer( strpos( $class, 'Exception' ) ) )
    {
        eval( sprintf( 'class %s extends Exception {}', $class ) );
        return;
    }

    if ( ! class_exists( $class, false ) )
    {
        // no exceptions in autoload :(
        die( sprintf( 'Failure to autoload class: "%s"', $class ) );
        // or perhaps: die ( '<pre>'.var_export( debug_backtrace(), true ).'</pre>' );        
    }
}

您还可以找到其他一些不太依赖 posix 的方法来迭代目录,但这就是我一直在使用的方法。

它遍历include_path(在php.ini或.htaccess中设置)中的所有目录来查找类或接口。

function __autoload( $class )
{
    $patterns = array( '%s.class.php', '%s.interface.php' );

    foreach( explode( ';', ini_get( 'include_path' ) ) as $dir )
    {
        foreach( $patterns as $pattern )
        {
            $file    = sprintf( $pattern, $class );
            $command = sprintf( 'find -L %s -name "%s" -print', $dir, $file );
            $output  = array();
            $result  = -1;

            exec( $command, $output, $result );

            if ( count( $output ) == 1 )
            {
                require_once( $output[ 0 ] );
                return;
            }
        }
    }

    if ( is_integer( strpos( $class, 'Exception' ) ) )
    {
        eval( sprintf( 'class %s extends Exception {}', $class ) );
        return;
    }

    if ( ! class_exists( $class, false ) )
    {
        // no exceptions in autoload :(
        die( sprintf( 'Failure to autoload class: "%s"', $class ) );
        // or perhaps: die ( '<pre>'.var_export( debug_backtrace(), true ).'</pre>' );        
    }
}

You could also find some other, less posix dependent way to iterate directories, but this is what i've been using.

It traverses all the directories in the include_path (set in php.ini or .htaccess) to find a class or interface.

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