在 AppServiceProvider 中添加错误处理时,会报 Class api.exception does not exist 错误

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //

        API::error(function (ModelNotFoundException $exception) {
            abort(404);
        });
    }
}

报错:
file

尝试过在config/api.php中添加exception配置,但是还是会报这个错误。

config/api.php中的exception配置:

'exception' => [
        \Dingo\Api\Exception\Handler::class,
        \Dingo\Api\Contract\Debug\ExceptionHandler::class
    ]
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 10

register 方法是用来绑定服务到服务容器的, 要想使用某个类实例的话, 最好放在boot方法中。

5年前 评论
liyu001989

file

报错是容器中不存在 api.exception,但是只要装了dingo,就应该被注册了,tinker 中试一下。拿项目源码试一下,对比看看哪的问题

5年前 评论

@liyu001989 我在tinker中测试显示exception的配置是有的,但是在register中绑定错误处理的时候就是会报错

file

5年前 评论
liyu001989
5年前 评论

对比源码之后发现,按照源码那种放到register里面去绑定的话就是会报错,放到boot里面绑定就是正常的,应该是生命周期的问题。目前是已经正常了,我再去捋一捋生命周期这块

5年前 评论

@luke05 个人也觉得 boot 里面更合适,注册只负责注册这一件事。注册好后还需要类似初始化的操作就放到 boot 里面。 不知道这样理解行不行

5年前 评论

@FreeMason 可以按你的理解... 需要使用服务容器中的服务时, 最好不要在register方法中使用,除非你很确定程序执行到该位置时你所引用的服务已经注册。在boot中所有的服务基本上都已经绑定好了, 可以直接使用

5年前 评论
  • @FreeMason、@luke05 说要在boot中注册的,其实还是没有理解生命周期。
  • API::error的代码如下
        public static function error(callable $callback)
        {
            return static::$app['api.exception']->register($callback);
        }
  • 上面这段代码的意思是:使用api.exception字符串去实例化一个\Dingo\Api\Exception\Handler对象,然后使用这个Handler对象的register方法,去注册一个异常注册handler。
  • 怀疑api.exception没有在appServiceProvider前注册成功的,也有一定道理。但是当你理解了注册serviceProvider的先后顺序后,就会明白api.exception早就已经在注册appServiceProvider前注册成功了。
  • 决定注册serviceProvider的先后顺序的代码在\Illuminate\Foundation\Application::registerConfiguredProviders中:
  • public function registerConfiguredProviders()
    {
        $providers = Collection::make($this->config['app.providers'])
                        ->partition(function ($provider) {
                            return Str::startsWith($provider, 'Illuminate\\');
                        });
    
        $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);
    
        (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                    ->load($providers->collapse()->toArray());
    }
  • 上述代码的含义是:

    • 先从config/app.php的providers数组中取出所有providers,然后按照是否以Illuminate关键字开头的标准分为两组。
    • 使用集合方法splice,将在第一组和第二组之间插入从composer自动产生的第三方扩展包providers数组。因此就分为了三组:系统组->第三方组->用户组
    • 但如果不是第一次执行,就会生成一个Provider的缓存文件:bootstrap/cache/services.php,这个缓存文件是按照provider的defer属性去分的。
    • 如果有缓存文件,那么在\Illuminate\Foundation\ProviderRepository::load方法中,就会去取缓存文件数组中的eager数组,可以看到缓存文件里面eager数组内Provider的顺序跟我们上面分析的顺序是一致的。

      public function load(array $providers)
      {
          $manifest = $this->loadManifest();
      
          // First we will load the service manifest, which contains information on all
          // service providers registered with the application and which services it
          // provides. This is used to know which services are "deferred" loaders.
          if ($this->shouldRecompile($manifest, $providers)) {
              $manifest = $this->compileManifest($providers);
          }
      
          // Next, we will register events to load the providers for each of the events
          // that it has requested. This allows the service provider to defer itself
          // while still getting automatically loaded when a certain event occurs.
          foreach ($manifest['when'] as $provider => $events) {
              $this->registerLoadEvents($provider, $events);
          }
      
          // We will go ahead and register all of the eagerly loaded providers with the
          // application so their services can be registered with the application as
          // a provided service. Then we will set the deferred service list on it.
          foreach ($manifest['eager'] as $provider) {
              $this->app->register($provider);
          }
      
          $this->app->addDeferredServices($manifest['deferred']);
      }
    • 缓存文件内的截图:
      file

    • 看到这里,有人可能会问,api.exception是在哪里注册了? 上面已经标明了是在Dingo\Api\Provider\LaravelServiceProvider
    • 进入LaravelServiceProvider,查看其注册方法:

      public function register()
      {
      parent::register();
      
      $this->registerRouterAdapter();
      }
    • 再看父类的注册方法:

      public function register()
      {
      $this->registerConfig();
      
      $this->registerClassAliases();
      
      $this->app->register(RoutingServiceProvider::class);
      
      $this->app->register(HttpServiceProvider::class);
      
      $this->registerExceptionHandler();
      
      $this->registerDispatcher();
      
      $this->registerAuth();
      
      $this->registerTransformer();
      
      $this->registerDocsCommand();
      
      if (class_exists('Illuminate\Foundation\Application', false)) {
          $this->commands([
              \Dingo\Api\Console\Command\Cache::class,
              \Dingo\Api\Console\Command\Routes::class,
          ]);
      }
      }
    • 可以看到有一个$this->registerExceptionHandler(),进去看看:
      protected function registerExceptionHandler()
      {
      $this->app->singleton('api.exception', function ($app) {
          return new ExceptionHandler($app['Illuminate\Contracts\Debug\ExceptionHandler'], $this->config('errorFormat'), $this->config('debug'));
      });
      }
    • 当当当当。。
5年前 评论

@hustnzj 分析的相当精彩 建议兄弟 整理整理 单独发博客

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!