随着我们的PHP5 OO应用程序的增长(无论是大小还是流量),我们决定重新审视__autoload()策略。
我们总是按照它包含的类定义来命名文件,因此Customer类将包含在Customer.php中。我们曾经列出了文件可能存在的目录,直到找到正确的.php文件。
这是非常低效的,因为你可能会经历许多你不需要的目录,并且每次请求都这样做(因此,需要加载stat()调用)。
我想到的解决方案......
- 使用指定目录名称的命名约定(类似于PEAR)。缺点:不会扩展太大,导致可怕的类名。
- 使用某种预先建立的位置数组(推进为其__autoload做这个)。缺点:在部署新代码之前需要重建。
- “动态”构建数组并缓存它。这似乎是最好的解决方案,因为它允许您想要的任何类名和目录结构,并且完全灵活,因为新文件只是添加到列表中。关注点是:存储它的位置以及删除/移动的文件。对于存储,我们选择了APC,因为它没有磁盘I / O开销。关于文件删除,没关系,因为你可能不想在任何地方要求它们。至于移动......那是没有解决的(我们忽略它,因为历史上它并不经常发生在我们身上)。
还有其他解决方案吗?
答案 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)
我过去曾使用过这个解决方案,我在博客上作了参考,可能对你们有些感兴趣...
答案 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中设置)以查找类或接口。