Лучшее решение для __autoload
Поскольку наше приложение PHP5 OO увеличилось (как по размеру, так и по трафику), мы решили пересмотреть стратегию __autoload().
Мы всегда называем файл определением класса, который он содержит, поэтому класс Customer будет содержаться в Customer.php. Мы использовали список каталогов, в которых файл потенциально может существовать, до тех пор, пока не будет найден правильный .php файл.
Это довольно неэффективно, потому что вы потенциально проходите через несколько каталогов, которые вам не нужны, и делайте это при каждом запросе (таким образом, создавая множество вызовов stat()).
Решения, которые приходят мне на ум...
-используйте соглашение об именах, которое диктует имя каталога (аналогично PEAR). Недостатки: не слишком масштабируется, что приводит к ужасным именам классов.
-доступайте с каким-то заранее построенным массивом местоположений (propel делает это для своей __autoload). Недостаток: требуется восстановление до развертывания нового кода.
-строить массив "на лету" и кешировать его. Это, по-видимому, лучшее решение, так как оно позволяет вам использовать любые имена классов и структуру каталогов и полностью гибко в том, что новые файлы просто добавляются в список. Вызывает озабоченность: где хранить его и что делать с удаленными/перемещенными файлами. Для хранения мы выбрали APC, поскольку у него нет служебных данных ввода-вывода на диске. Что касается удаления файлов, это не имеет значения, так как вы, вероятно, не захотите их нигде в любом случае. Что касается движений... это нерешенное (мы игнорируем его, поскольку исторически это не случалось очень часто для нас).
Любые другие решения?
Ответы
Ответ 1
Я тоже долгое время играл с автозагрузкой, и в итоге я создал какой-то автозагрузчик с именами (да, он также работает и для PHP5.2).
Стратегия довольно проста:
Сначала у меня есть одноэлементный класс (загрузчик), который имеет вызов, который имитирует import
. Этот вызов принимает один параметр (полное имя класса для загрузки) и внутренне вычисляет имя файла, из которого он был вызван (используя debug_backtrace()
). Вызов хранит эту информацию в ассоциативном массиве, чтобы использовать ее позже (используя вызывающий файл в качестве ключа и список импортированных классов для каждого ключа).
Типичный код выглядит следующим образом:
<?php
loader::import('foo::bar::SomeClass');
loader::import('foo::bar::OtherClass');
$sc = new SomeClass();
?>
При запуске автозагрузки полное имя класса, которое было сохранено в массиве, преобразуется в реальное местоположение файловой системы (двойные двоеточия заменяются разделителями каталогов), и в результате появляется имя файла.
Я знаю, что это не то, о чем вы просите, но может решить проблему обхода каталога, так как загрузчик напрямую знает, где именно находится файл (с добавленной функцией, которую вы могли бы организовывать в своих каталогах, с нет очевидного снижения производительности).
Я мог бы предоставить вам некоторые рабочие примеры, но я слишком застенчив, чтобы показать мой дерьмовый код публике. Надеюсь, что приведенное выше объяснение было полезно...
Ответ 2
Существует два общих подхода, которые хорошо работают.
Сначала используется иерархическая структура имен классов PEAR, поэтому вам просто нужно заменить '_' на /, чтобы найти класс.
http://pear.php.net/manual/en/pear2cs.rules.php
Или вы можете искать каталоги для классов php и имя класса карты в файл. Вы можете сохранить карту класса в кеш, чтобы сохранить каталоги поиска на каждой странице.
Структура Symfony использует этот подход.
Как правило, лучше следовать стандартной структуре как ее более простой, и вам не нужно ничего кэшировать, плюс вы следуете рекомендациям.
Ответ 3
Мы используем нечто похожее на последний вариант, за исключением проверки file_exists() перед требованием. Если он не существует, перестройте кеш и попробуйте еще раз. Вы получаете дополнительный stat за файл, но он обрабатывает движения прозрачно. Очень удобно для быстрого развития, когда я часто перемещаю или переименовываю вещи.
Ответ 4
Я использовал это решение в прошлом, я писал об этом для справки и может быть интересен некоторым из вас...
Здесь это
Ответ 5
CodeIgniter делает что-то подобное с функцией load_class. Если я правильно помню, это статическая функция, которая содержит массив объектов. Вызов функции:
load_class($class_name, $instansiate);
поэтому в вашем случае
load_class('Customer', TRUE);
и это приведет к загрузке экземпляра класса Customer в массив объектов.
Функция была довольно прямой. Извините, я не могу вспомнить имя класса, в котором он был. Но я помню, что было несколько классов, которые загружаются, например, я считаю класс Routing, класс Benchmark и класс URI.
Ответ 6
Если вы используете APC, вы должны включить кеширование кода операции. Я считаю, что это будет иметь большую выгоду для производительности и будет более прозрачным решением, чем любая используемая вами стратегия класса/файла.
Второе предложение состоит в том, чтобы использовать любую стратегию класса/файла, которую легче всего разрабатывать, но на вашем производственном сайте вы должны конкатенировать классы, используемые чаще всего в одном файле, и следить за тем, чтобы они загружались в течение каждого запроса (или кэшировались с помощью APC).
Проведите некоторые эксперименты по производительности с увеличением набора ваших классов. Вы, вероятно, обнаружите, что преимущество сокращения ввода-вывода файлов настолько значимо, что объединение всех классов в один файл - это чистая победа, даже если вам не нужны все классы во время каждого запроса.
Ответ 7
У меня есть конкретные соглашения об именах для каждого типа типа (контроллеры, модели, файлы библиотек и т.д.), поэтому в настоящее время я делаю что-то похожее на:
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?
}
}
Ответ 8
Старая ветка, но я думал, что могу вообще разоблачить свой метод здесь, возможно, это может помочь кому-то. Именно так я определяю __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
Быстро, и если файл отсутствует, он будет аварийно завершен, и ваш журнал ошибок скажет вам, какой файл отсутствует. Это может показаться немного резким, но если вы попытаетесь использовать неправильный класс, это ваша проблема.
NB: это было для PHP 5 < 5.3, поэтому для PHP 5.3 вы можете использовать пространства имен, причина, по которой я использовал 3_ в качестве разделителя, заключается в том, что это легкая замена для использования пространства имен 5.3
Ответ 9
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!");
}
}
Ответ 10
Вы исследовали с помощью Zend_Loader (с registerAutoload()), а не только нативный __autoload()
? Я использовал Zend_Loader и был доволен этим, но не смотрел на него с точки зрения производительности. Тем не менее, этот пост в блоге, похоже, сделал некоторый анализ производительности на нем; вы можете сравнить эти результаты со своим собственным тестированием производительности на вашем текущем автозагрузчике, чтобы убедиться, что он может оправдать ваши ожидания.
Ответ 11
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), чтобы найти класс или интерфейс.