【问题标题】:Best way to capture errors from custom spl_autloader functions从自定义 spl_autloader 函数中捕获错误的最佳方法
【发布时间】:2011-11-03 01:23:21
【问题描述】:

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

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

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

【问题讨论】:

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


    【解决方案1】:

    免责声明:我不一定认为这是一个很好的解决方案,尤其是一些回调会被重复,但我会将其作为潜在的灵感来源。

    我刚刚对此进行了修改,并提出了一个自动加载方法,如果在自动加载尝试中使用该方法,如果未找到该类,则会在序列末尾抛出异常。它通过在自动加载过程发生时修改自动加载堆栈并递归调用自动加载来实现这一点。所有这些魔法的好处是只有在所有回调都失败时才会抛出异常,并且它不需要虚拟回调,这会干扰以后注册的回调。

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

    【讨论】:

    • 我选择多台自动装弹机的原因之一是性能/速度。如果一个人只使用一个自动加载函数并在该函数中解析/匹配类名字符串,那么抛出异常就没有问题,因为你只有一个包含要处理。这种方法的缺点是您需要为简单地加载类而消耗大量的处理能力。对于多个自动加载器,不会发生太多处理,因为每个自动加载器负责特定的类/路径段。
    • 你是对的,当然。我认为我不会在生产环境中使用它,因为正如您所说,查找每个类的开销相当高。这或多或少是由确保仅在所有回调失败时才抛出异常的想法驱动的(即使是在Autoloader 之后添加的那些。我可能稍后再看一下,看看我是否可以改进它(是当我发布它时,已经过了我的就寝时间)。
    猜你喜欢
    • 2017-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-10
    • 2010-10-16
    • 1970-01-01
    相关资源
    最近更新 更多