По мере того как наше объектно-ориентированное приложение PHP5 росло (как в размере, так и в трафике), мы решили пересмотреть стратегию __autoload ().
Мы всегда называем файл по определению класса, который он содержит, поэтому класс Customer будет содержаться в Customer.php. Раньше мы составляли список каталогов, в которых потенциально может существовать файл, до тех пор, пока не был найден нужный файл .php.
Это довольно неэффективно, потому что вы потенциально просматриваете ряд каталогов, которые вам не нужны, и делаете это при каждом запросе (таким образом, выполняя множество вызовов stat ()).
Решения, которые приходят мне в голову ...
-использовать соглашение об именах, которое диктует имя каталога (аналогично PEAR). Недостатки: не слишком велико масштабируется, что приводит к ужасным названиям классов.
- придумать какой-то заранее созданный массив локаций (propel делает это для своего __autoload). Недостаток: требуется перестройка перед развертыванием нового кода.
-создать массив «на лету» и кешировать его. Это кажется лучшим решением, поскольку оно позволяет использовать любые имена классов и структуру каталогов, которые вы хотите, и является полностью гибким в том, что новые файлы просто добавляются в список. Проблемы следующие: где его хранить и как насчет удаленных / перемещенных файлов. В качестве хранилища мы выбрали APC, поскольку у него нет накладных расходов на дисковый ввод-вывод. Что касается удаления файлов, это не имеет значения, поскольку вы, вероятно, в любом случае не захотите их где-либо требовать. Что касается ходов ... это нерешенный вопрос (мы его игнорируем, так как исторически это происходило с нами нечасто).
Какие-нибудь другие решения?






CodeIgniter делает нечто подобное с функцией load_class. Если я правильно помню, это статическая функция, которая содержит массив объектов. Вызов функции:
load_class($class_name, $instansiate);
так что в вашем случае
load_class('Customer', TRUE);
и это загрузит экземпляр класса Customer в массив объектов.
Функция была довольно простой. Извините, я не могу вспомнить имя класса, в котором он находился. Но я помню, что загружалось несколько классов, например, я думаю, класс Routing, класс Benchmark и класс URI.
Вы исследовали использование Zend_Loader (с registerAutoload ()) вместо собственного __autoload()? Я использовал Zend_Loader и был доволен им, но не смотрел на него с точки зрения производительности. Однако это сообщение в блоге, похоже, провел некоторый анализ производительности; вы можете сравнить эти результаты с результатами собственного тестирования производительности вашего текущего автозагрузчика, чтобы убедиться, что он соответствует вашим ожиданиям.
Я также довольно долго играл с автозагрузкой, и в итоге я реализовал своего рода автозагрузчик с пространством имен (да, он также работает для PHP5.2).
Стратегия довольно проста:
Сначала у меня есть одноэлементный класс (загрузчик), у которого есть вызов, имитирующий import. Этот вызов принимает один параметр (полное имя загружаемого класса) и внутренне вычисляет имя файла, из которого он был вызван (с использованием debug_backtrace()). Вызов сохраняет эту информацию в ассоциативном массиве, чтобы использовать ее позже (используя вызывающий файл в качестве ключа и список импортированных классов для каждого ключа).
Типичный код выглядит так:
<?php
loader::import('foo::bar::SomeClass');
loader::import('foo::bar::OtherClass');
$sc = new SomeClass();
?>
Когда запускается автозагрузка, полное имя класса, которое было сохранено в массиве, преобразуется в реальное местоположение файловой системы (двойные двоеточия заменяются разделителями каталогов), и включается полученное имя файла.
Я знаю, что это не совсем то, о чем вы просили, но это может решить проблему обхода каталогов, поскольку загрузчик напрямую знает, где именно находится файл (с добавленной функцией, что вы можете организовать свои классы в каталогах без видимой производительности штраф).
Я мог бы предоставить вам несколько рабочих примеров, но я стесняюсь показывать публике свой дрянной код. Надеюсь, что приведенное выше объяснение было полезно ...
Возможно, вы правы, если в вашем проекте используется несколько классов, но для крупных объектно-ориентированных проектов вам придется перейти на другое решение. Представьте, что php ищет один класс во всех каталогах огромного фреймворка. Жалко без каких-либо подсказок по импорту.
Если вы используете APC, вам следует включить кеширование кода операции. Я считаю, что это принесет больше пользы для производительности и будет более прозрачным решением, чем любая используемая вами стратегия класса / файла.
Второе предложение - использовать любую стратегию класса / файла, с которой легче всего разрабатывать, но на вашем рабочем сайте вы должны объединить классы, которые наиболее часто используются, в один файл и убедиться, что он загружается во время каждого запроса (или кешируется с помощью APC).
Поэкспериментируйте с производительностью с увеличивающимися наборами ваших классов. Вы, вероятно, обнаружите, что преимущество сокращения файлового ввода-вывода настолько велико, что объединение всех классов в один файл является чистой выгодой, даже если вам не нужны все классы во время каждого запроса.
Конечно, я использую кеширование опкодов. Просто APC не мешает вам вызывать stat () несколько раз, поскольку он срабатывает только после require ().
Что касается второго пункта, учитывая количество классов, преимущество нивелируется тем фактом, что мы загружаем намного больше, чем необходимо. Кроме того, действительно неудобно перестраивать его каждый раз, когда вы хотите что-то развернуть.
Вы измерили производительность и сравнили результаты, или вы просто делаете обоснованное предположение?
Есть 2 общих подхода, которые хорошо работают. Сначала используется стандартная структура именования классов PEAR, поэтому вам просто нужно заменить '_' на /, чтобы найти класс.
http://pear.php.net/manual/en/pear2cs.rules.php
Или вы можете искать в каталогах классы php и отображать имя класса в файл. Вы можете сохранить карту классов в кеш, чтобы сохранять поисковые каталоги при каждой загрузке страницы. Фреймворк Symfony использует этот подход.
Как правило, лучше следовать стандартной структуре, поскольку она проще, и вам не нужно ничего кэшировать, к тому же вы следуете рекомендуемым рекомендациям.
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), чтобы найти класс или интерфейс.
OMG, это ужасно. Вы вызываете find для каждого каталога в INCLUDE_PATH, для каждого загружаемого вами класса, для каждого запроса PHP?!?
И ваш разделитель пути - ";" (что означает, что этот код работает только в Windows), но вы полагаетесь на такой инструмент posix, как find?
Я согласен ... это ужасно неэффективно. Почему не file_exists ()? Вот что у нас есть на данный момент.
Проверьте RecursiveDirectoryIterator.
Извините, Билл, вы ошиблись в обоих аккаунтах. Я не работаю в Windows, но я кэширую расположение файлов в базе данных, это используется только тогда, когда они не могут быть там найдены. Помните, это всего лишь образец умных людей ...
Престон: у меня в сочетании с RecursiveIteratorIterator он выполняет свою работу с меньшим количеством кода и почти так же быстро (ymmv)
tpk: file_exists может принимать только путь к файлу, вы не можете использовать его в более глубоких вложенных ситуациях.
Мне нравится, как вы динамически создаете исключения. С другой стороны, это вроде как eval..erm evil. Сорта горько-сладкая
В дополнение к недостаткам, на которые указали другие: 1) Исключения должны быть где-то определены, чтобы другие могли их найти и увидеть комментарий, который объясняет, при каких обстоятельствах они будут выброшены. 2) Die () ing, когда класс не существует, предоставляет меньше полезную информацию, чем просто естественный сбой вызывающего кода.
@too much php, если вы прочитаете перед публикацией, вы увидите, что исключения загружаются, генерируются только в том случае, если нет определения. Таким образом, вы можете генерировать исключение WhateverTheHeckYouNeedException без реализации определения, большинство исключений, которые вы генерируете, в любом случае будут расширять Exception только по имени.
Мы используем что-то похожее на последнюю опцию, за исключением проверки file_exists () перед требованием. Если его нет, перестройте кеш и попробуйте еще раз. Вы получаете дополнительную статистику для каждого файла, но она прозрачно обрабатывает перемещения. Очень удобно для быстрой разработки, когда я часто перемещаю или переименовываю вещи.
Я использовал это решение в прошлом, я писал о нем для справки и может быть интересен некоторым из вас ...
У меня есть определенные соглашения об именах для каждого «типа» класса (контроллеры, модели, файлы библиотек и так далее ...), поэтому в настоящее время я делаю что-то подобное:
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?
}
}
Старая ветка, но я подумал, что все равно могу раскрыть здесь свой метод, может быть, это кому-то поможет. Вот как я определяю __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.
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!");
}
}
Два больших преимущества автозагрузки: 1) отсутствие необходимости вручную загружать классы перед их использованием и 2) отсутствие жесткого кодирования их местоположения в файловой системе. Вы отменили оба.