10

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

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

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

我想到的解决方案...

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

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

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

还有其他解决方案吗?

4

11 回答 11

2

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

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

典型代码如下所示:

<?php

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

    $sc = new SomeClass();

?>

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

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

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

于 2008-12-11T16:50:53.530 回答
1

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

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

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

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

于 2008-12-12T01:36:49.773 回答
1

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

于 2009-01-10T03:28:12.880 回答
1

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

这里是

于 2009-07-31T19:46:16.477 回答
0

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

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

所有 PHP 文件都以树的形式组织

/path/to/root/php
  /应用
    /网站
      服务器.php
  /模型
    用户.php
  /图书馆
    /HTTP
      客户端.php
    套接字.php

类名是:

应用程序___网站___服务器
型号___用户
库___HTTP___客户端
库___Socket

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

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

于 2010-06-16T13:19:36.593 回答
0

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

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

做一些性能实验,增加你的类集。您可能会发现减少文件 I/O 的好处是如此显着,即使您在每次请求期间都不需要所有类,将所有类放在一个文件中也是一种净赢。

于 2008-12-11T17:49:22.323 回答
0

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


 load_class($class_name, $instansiate);

所以在你的情况下


 load_class('Customer', TRUE);

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

该功能非常简单。抱歉,我不记得它所在的类的名称。但我确实记得有几个类被加载,例如我认为 Routing 类、Benchmark 类和 URI 类。

于 2008-12-11T16:14:42.137 回答
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?
    }
}
于 2009-09-14T23:55:27.080 回答
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!");
   }
}
于 2014-12-14T18:08:50.937 回答
-1

您是否调查过使用Zend_Loader(使用registerAutoload())而不仅仅是 native __autoload()?我使用过 Zend_Loader 并且对它很满意,但还没有从性能的角度来看待它。不过这篇博文好像对它做了一些性能分析;您可以将这些结果与您在当前自动装载机上进行的内部性能测试进行比较,看看它是否能满足您的期望。

于 2008-12-11T16:27:22.377 回答
-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 中设置)以查找类或接口。

于 2008-12-12T14:32:16.047 回答