从自定义spl_autloader函数捕获错误的最佳方法

时间:2011-11-03 01:23:21

标签: php model-view-controller exception include autoload

为了更深入地了解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队列中的第二个或第三个自动加载方法可以找到所请求的文件/类。

为了适应这种行为,我发现我必须创建第三个“假”自动加载方法,它是队列中最后一个被调用的方法 - 此方法检查错误标志,如果设置,则抛出例外。

在一个冗长的问题中,我真正要问的是 - 是否有更好的方法来捕获失败的包含,并且一旦所有自动加载功能都运行了,就采取了相应的行动?

1 个答案:

答案 0 :(得分:0)

免责声明:我不一定认为这是一个很好的解决方案,特别是因为一些回调会被复制,但我会把它作为潜在的灵感来源。

我刚才有一个小提琴,并提出了一个自动加载方法,如果在自动加载尝试中使用它,如果找不到类,将在序列的末尾抛出异常。它通过在自动加载过程发生时修改自动加载堆栈并以递归方式调用自动加载来完成此操作。所有这些魔法的好处是,只有在所有回调都失败的情况下才会抛出异常,并且它不需要虚拟回调,这会干扰以后注册的回调。

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