使用Laravel IoC容器

时间:2015-10-15 11:04:43

标签: laravel ioc-container laravel-5.1

我需要一种根据请求参数通过IoC提供程序加载对象的方法。现在我直接用我要重构的App::make(xy, [$urlParamter]加载我的对象,这样我就可以按照预期的方式使用依赖注入。为了描述我目前的架构,我需要向你展示一些安静的信息,最后你会发现我对它的具体问题。

我正在构建一个通用的CMS框架,它提供了一个可以使用自定义导入实现进行扩展的导入体系结构。 现在我正在努力通过IoC容器正确加载具体类,因为它们总是依赖于所选的导入。

要深入研究我的问题,这是routes.php

中的切入点
Route::get('/import', ['as' => 'overview', 'uses' => '\CMSFramework\Http\Controllers\Import\ImportController@index']);

这会生成一个视图,用户可以选择要触发的具体导入。选择具体导入后,用户应获取单独的视图以准备相应的导入(即上载CSV文件,选择要导入的区域等)。

在我的概念中,导入实现包括:

  • 控制器类,用于实现特定(peraration)任务,例如上载CSV文件。它继承自cms框架的基本控制器
  • 进口商业"或者"服务" class,实现数据的导入方式(并可以进一步委托排队的作业等。)

CMS框架部分包括:

  • 所有常见/共享导入任务的基本控制器类,例如(启动准备导入,清除所有工作数据等)
  • 所有实现继承自的基本服务类ImportBase。它提供了一个接口,用于接收任何导入的进度并实现共享操作,如清理工作数据等。)
  • ImportStatus类是ImportBase - 类的一部分,通过$ImportBase->status()处理所有运行时状态信息(例如"作业仍在运行,进展如何)。该类还为所谓的"有效载荷提供了一个容器。允许任何conrete导入实现推送和获取自定义状态信息(即任何子流程已完成)

回到我的IoC架构。在用户选择具体导入后,以下路由将操作委派给自定义导入实现的控制器。如果它是框架支持的标准操作,例如通过URL /import/<importkey>/clean,则cms框架的继承BaseController接管并处理请求

Route::get('/import/{key}/{method}', ['uses' => function($key, $method) {
    return App::make('\\MadeleinePim\\Http\\Controllers\\Import\\'.ucfirst(camel_case($key)).'Controller')->$method($key);
}]);

我知道通过命名约定的这种直接绑定可以改进(可能通过自定义配置文件),但是现在这对我有用。

现在我需要展示一个示例,说明我是如何尝试通过/import/<importkey>/seedCsvDataToDatabase在我的控制器中实现具体导入目标的:

public function seedCsvDataToDatabase($key)
{
    // The IoC binding is shown in next code snippet. I did not found a good way to use method injection because
    // of the route-specific parameters that control the responsible import implementation 
    $import = \App::make(Import::class, [$key]);

    // Now trigger the import service operation of that concrete import implementation (probably bad design here)
    $import->seed();

    // Now, that this preparation task is done, I use the ImportStatus object which is part of the Import to store
    // status informations. With this I can then decided in which step the user is (Think of it like a wizard to
    // prepare any import)
    $import->status()
        ->set(ConcreteImport::STATUS_SEEDED, true)
        ->set(ConcreteImport::STATUS_SEEDED_DURATION_SECONDS, (microtime(true) - $time_start) / 60);

    // Back to controller method that determines in which status the import is to delegate/redirect to different
    // views.
    return redirect('/import/<importkey>');
}

我对导入类的IoC绑定:

$this->app->singleton(Import::class, function ($app, array $parameters) {
    $importKey = head($parameters);

    // There is a config file that provides the class names of the concrete import implementations
    $importClassName = config()->get('import.' . $importKey);

    if (!$importClassName) {
        throw new ImportNotFoundException($importKey, "Import with key '{$importKey}' is not setup properly'");
    }

    $importReflectionClass = new \ReflectionClass($importClassName);

    return $importReflectionClass->newInstance($importKey);
});

最后,导入状态的延迟加载(封装在ImportStatus对象中看起来像这样

public function status()
{
    if (!$this->status) {
        $this->status = \App::make(ImportStatus::class, [$this->key()]);
    }

    return $this->status;
}

我希望这能说明我尝试从IoC容器中解析导入对象的方式。

到目前为止,我的学习是,这不是注入我的物体的正确方法。

假设是正确的,我不应该在运行时将$importKey传递给App::make(),而应该尝试将其设为独立吗?

我对此失败的尝试是让IoC绑定更智能,让它访问请求以正确注入我的具体导入对象所需的$importKey,如(伪代码!):

$this->app->bind(ImportStatus::class, function(Container $app) {
    // Did not find a good way to access the {key}-part of my route /import/{key}/{method}
    $key = $app->make(Request::class)->get('key'); // Does not work like this
    return new \Scoop\Import\ImportStatus($key);
});
  • 这种方法可以这样工作吗?
  • 我可以以某种方式从我的路线中通过$importKey到ServiceProvider(或者更好地从那里拉出来吗?)
  • 有没有更好的解决方案来初始化我的具体导入实现?

----------

更新1

对于我在IoC Binding中访问Route的最直接的想法,我这样做了:

$this->app->singleton(Import::class, function (Container $app) {
    $importKey = \Route::current()->getParameter('key');

    $importClassName = config()->get('import.' . $importKey);

    $importReflectionClass = new \ReflectionClass($importClassName);

    return $importReflectionClass->newInstance($importKey);
});

然而,@ Sandyandi N. dela Cruz使用路由器绑定的想法阻止了Binding和Request之间的直接耦合,这仍然感觉不对。使用路由器绑定将请求参数耦合到实现,听起来更合适。

1 个答案:

答案 0 :(得分:1)

我认为您已经在IoC容器上花了很多时间。为什么不实现Factory模式并执行路由绑定而不是创建多个控制器来处理不同的Import?粗略的例子如下:

  1. 创建路线活页夹 - 修改app/Provider/RouteServiceProvider.php boot()方法
  2. public function boot(Router $router)
    {
        parent::boot($router);
    
        // Add the statement below.
        $router->bind('import', 'App\RouteBindings@import');
    }
    
    1. App\RouteBindings课程设为app/RouteBindings.php
    2. 使用以下内容创建import()方法:
    3. public function import($importKey, $route)
      {
          switch ($importKey) {
              case 'import_abc':
                  return new ImportAbc;
                  break; // break; for good measure. ;)
              case 'import_xyz':
                  return new ImportXyz;
                  break;
              // and so on... you can add a `default` handler to throw an ImportNotFoundExeption.
          }
      }
      
      1. 创建解析Import类的路径。
      2. Route::get('import/{import}/{method}', 'ImportController@handleImport');

        此处,{import}将根据您的网址返回正确的Import具体类。

        1. ImportController handleImport()您可以执行以下操作:
        2. public function handleImport(Import $import, $method)
          {
              // $import is already a concrete class resolved in the route binding.
              $import->$method();
          }
          

          所以当你点击:http://example.com/import/import_abc/seed时,路径绑定将返回一个具体的ImportAbc类,并将其存储在$import方法的handleImport()上,然后存储在handleImport()中1}}方法将执行:$import->seed();。提示:您应该将其他控制器逻辑(例如$import->status()->set())移动到Import类中。保持控制器薄。

          只需确保您的Import类具有相同的签名。

          它有点像Laravel的Route Model Binding,除了你为绑定创建逻辑。

          同样,这只是一个粗略的例子,但我希望它有所帮助。