Лучшее решение для __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_LoaderregisterAutoload()), а не только нативный __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), чтобы найти класс или интерфейс.