__autoload的最佳解决方案

时间:2008-12-11 15:57:33

标签: php oop autoload

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

我们总是按照它包含的类定义来命名文件,因此Customer类将包含在Customer.php中。我们曾经列出了文件可能存在的目录,直到找到正确的.php文件。

这是非常低效的,因为你可能会经历许多你不需要的目录,并且每次请求都这样做(因此,需要加载stat()调用)。

我想到的解决方案......

- 使用指定目录名称的命名约定(类似于PEAR)。缺点:不会扩展太大,导致可怕的类名。

- 使用某种预先建立的位置数组(推进为其__autoload做这个)。缺点:在部署新代码之前需要重建。

- “动态”构建数组并缓存它。这似乎是最好的解决方案,因为它允许您想要的任何类名和目录结构,并且完全灵活,因为新文件只是添加到列表中。关注点是:存储它的位置以及删除/移动的文件。对于存储,我们选择了APC,因为它没有磁盘I / O开销。关于文件删除,没关系,因为你可能不想在任何地方要求它们。至于移动......那是没有解决的(我们忽略它,因为历史上它并不经常发生在我们身上)。

还有其他解决方案吗?

11 个答案:

答案 0 :(得分:2)

我也玩过自动加载很长一段时间了,最​​后我实现了某种命名空间自动加载器(是的,它也适用于PHP5.2)。

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

典型代码如下所示:

<?php

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

    $sc = new SomeClass();

?>

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

我知道这不是你要求的,但它可以解决目录遍历问题,因为加载器直接知道文件的确切位置(添加的功能可以让你的类在目录中组织,没有明显的性能损失)。

我可以为你提供一些有用的例子,但是我太害羞了,不能向公众展示我糟糕的代码。希望上面的解释很有用......

答案 1 :(得分:1)

有两种方法可以很好地发挥作用 首先是使用PEAR标准类命名结构,所以你只需要用/替换'_'来找到类。

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

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

一般来说,最好遵循标准结构,因为它更简单,你不需要缓存任何东西,而且你遵循建议的指导方针。

答案 2 :(得分:1)

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

答案 3 :(得分:1)

我过去曾使用过这个解决方案,我在博客上作了参考,可能对你们有些感兴趣...

Here it is

答案 4 :(得分:0)

CodeIgniter与load_class函数类似。如果我没记错的话,它是一个包含对象数组的静态函数。对该功能的调用是:


 load_class($class_name, $instansiate);

所以在你的情况下


 load_class('Customer', TRUE);

这会将Customer类的一个实例加载到objects数组中。

这个功能非常简单。对不起,我无法记住它所在类的名称。但我记得有几个类被加载,比如我认为路由类,Benchmark类和URI类。

答案 5 :(得分:0)

如果您正在使用APC,则应启用操作码缓存。我相信这会比您使用的任何类/文件策略更有利于性能,并且是一个更透明的解决方案。

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

使用增加的类集进行一些性能实验。您可能会发现减少文件I / O的好处是如此重要,即将所有类填充到一个文件中是一个净赢,即使您在每个请求期间都不需要所有类。

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

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

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&lt; 5.3,所以对于PHP 5.3你可能会使用命名空间,我之所以使用3 _作为分隔符是因为它是5.3命名空间使用的简单替代

答案 8 :(得分:0)

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!");
   }
}

答案 9 :(得分:-1)

您是否使用Zend_Loader(使用registerAutoload())而不仅仅使用原生__autoload()进行了调查?我已经使用过Zend_Loader并对它感到满意,但是从性能角度来看并没有看到它。但是,this blog post似乎已对其进行了一些性能分析;您可以将这些结果与您当前自动加载器上的内部性能测试进行比较,看看它是否符合您的期望。

答案 10 :(得分:-3)

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中设置)以查找类或接口。