Лучший способ фиксировать ошибки из пользовательских функций spl_autloader

#php #model-view-controller #exception #include #autoload

#php #модель-представление-контроллер #исключение #включить #автозагрузка

Вопрос:

Чтобы получить более глубокое понимание парадигмы MVC, я экспериментирую с созданием собственного фреймворка.

Вместо того, чтобы полагаться на уродливую ошибку / предупреждение PHP, возникающую в результате неудачного включения, я настроил общий класс исключений, который представляет данные в более удобочитаемом формате вместе с трассировкой стека.

Вот код, который у меня есть на данный момент…

 spl_autoload_register(__NAMESPACE__.'Autoloader::coreLoader');
spl_autoload_register(__NAMESPACE__.'Autoloader::appLoader');
spl_autoload_register(__NAMESPACE__.'Autoloader::throwException');

class Autoloader
{
    private static $isError = false;

    private static function loadHelper($className)
    {
        //Relevant code here
    }

    public static function coreLoader($className)
    {
        $classPath = static::loadHelper($className);

        if (!@include PRIVATE_ROOT.DIRECTORY_SEPARATOR.$classPath.PHPEXT)
        {
            static::$isError = true;
        }
        else
        {
            static::$isError = false;
        }
    }

    public static function appLoader($className)
    {
        $classPath = static::loadHelper($className);

        if (!@include SYSTEM_PATH.DIRECTORY_SEPARATOR.$classPath.PHPEXT)
        {
            static::$isError = true;
        }
        else
        {
            static::$isError = false;
        }
    }

    public static function throwException($className)
    {
        if (static::$isError)
        {
            throw new Exception_General('Attempted to load file: '.$className);
        }
    }
}
  

Учитывая тот факт, что include не генерируется исключение, когда файл не найден, я не могу использовать блок try / catch .

Вместо блока try / catch я обнаружил, что можно использовать if statement as в приведенном выше коде, чтобы проверить include , была ли успешно выполнена загрузка требуемого файла.

Мой Exception_General класс отвечает за генерацию и отображение удобных для разработчика выходных данных / сообщений об ошибках. Проблема, с которой я сталкиваюсь здесь, заключается в том, что если я создам исключение в рамках законного метода автоматической загрузки, скрипт по праву остановится.

Это, конечно, не идеально, поскольку, хотя первый метод автозагрузки может не найти запрошенный класс, 2-й или 3-й метод автозагрузки в очереди spl_autoload МОЖЕТ найти запрошенный файл / класс.

Чтобы приспособиться к такому поведению, я обнаружил, что мне пришлось создать 3-й «поддельный» метод автоматической загрузки, который вызывается последним в очереди — этот метод проверяет флаг ошибки и, если он установлен, выдает исключение.

В немного длинном вопросе я действительно спрашиваю: есть ли лучший способ фиксировать неудачные включения, и как только все функции автозагрузки будут выполнены, действуйте соответствующим образом?

Ответ №1:

Отказ от ответственности: я не обязательно думаю, что это отличное решение, особенно потому, что некоторые обратные вызовы будут дублироваться, но я использую его как потенциальный источник вдохновения.

Я только что повозился с этим и придумал метод автоматической загрузки, который, если он используется при попытке автоматической загрузки, выдаст исключение в конце последовательности, если класс не найден. Это делается путем изменения стека автозагрузки во время выполнения процедуры автозагрузки и рекурсивного вызова автозапуска. Преимущество всего этого волшебства заключается в том, что исключение будет выдаваться только в том случае, если все обратные вызовы завершаются неудачно, и для этого не требуется фиктивный обратный вызов, который будет мешать более поздним зарегистрированным обратным вызовам.

 function a() { echo "an"; }
function b() { echo "bn"; }

class Autoloader {
  // This prevents infinite recursion.
  static $loading = false;

  // This is the callback for this autoloader, for convenience.
  static $callback = ['Autoloader', 'autoload'];

  static function autoload($class_name) {
    if(!static::$loading) {
      // Basically what we do here is repeat the autoload cycle, and if it fails
      // throw an exception.
      $autoloaders = spl_autoload_functions(); // All registered autoloaders.
      $priority = array_search(static::$callback, $autoloaders);
      $past = []; // An array for callbacks that have already occurred.
      for($i = 0; $i < $priority; $i  ) {
        $past[] = $autoloaders[$i]; // Fill the past events
        spl_autoload_unregister($autoloaders[$i]); // Remove it from the stack
      }
      // We have now taken off the callbacks that ran before this one

      static::$loading = true; // Make sure we don't get stuck.
      spl_autoload_call($class_name); // 'Resume' autoloading.
      static::$loading = false; // Reset for next time.

      // We now need to put the other callbacks back in.
      for($i = count($past) - 1; $i >= 0; $i--)
        spl_autoload_register($past[$i], true, true);

      if(class_exists($class_name))
        return true; // All is well
      throw new Exception('could not find class '.$class_name); // Not so well...
    } else {
      echo "Autoloader::autoloadn";
    }
  }
}

spl_autoload_register('a');
spl_autoload_register(Autoloader::$callback);
spl_autoload_register('b');

new Foo; // Would print `a, Autoloader::autoload, b` then the exception message.
  

Одно из предостережений заключается в том, что обратные вызовы, возникающие после Autoloader::autoload , будут выполняться дважды, поскольку исключение фактически не вырвется из цепочки автозагрузки. Хотя можно было бы удалить эти обратные вызовы перед созданием исключения, это нарушило бы последовательность автозагрузки, если исключение было поймано (например, если оно ['a', 'Autoloader...', 'c'] было в стеке, а класс не был найден, либо c будет выполнено дважды, как в приведенном выше коде, либо код можно изменить, чтобы сделатьпроисходит c один раз, но если исключение было перехвачено, стек автозагрузки будет оставлен как ['a', 'Autoloader...'] ).

Комментарии:

1. Одна из причин, по которой я решил пойти по пути нескольких автозагрузчиков, была связана с производительностью / скоростью. Если кто-то использует только одну функцию автозапуска и в рамках этой функции анализирует / сопоставляет строки имен классов, проблем с выдачей исключения не возникнет, поскольку у вас есть только одно включение, с которым нужно бороться. Недостатком этого подхода является то, что вы затрачиваете много вычислительной мощности для простой загрузки классов. При использовании нескольких автозагрузчиков обработка выполняется не так много, поскольку каждый автозагрузчик отвечает за определенный сегмент классов / путей.

2. Вы правы, конечно. Я не думаю, что это то, что я бы использовал в производственной среде, поскольку, как вы говорите, накладные расходы на поиск каждого класса довольно высоки. Это было более или менее обусловлено идеей обеспечения того, чтобы исключение создавалось только в случае сбоя всех обратных вызовов (даже тех, которые добавляются после Autoloader . Возможно, я еще раз взгляну на это позже и посмотрю, смогу ли я его улучшить (когда я его опубликовал, мне уже давно пора было спать).