Лучший способ фиксировать ошибки из пользовательских функций spl_autloader - PullRequest
1 голос
/ 03 ноября 2011

Чтобы глубже понять парадигму 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, как в коде выше, чтобы проверить, успешно ли оператор include загрузил требуемый файл.

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

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

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

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

1 Ответ

0 голосов
/ 03 ноября 2011

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

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

function a() { echo "a\n"; }
function b() { echo "b\n"; }

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::autoload\n";
    }
  }
}

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...']).

...