使用来自 Laravel IoC 容器的参数加载对象

Load objects with parameters from Laravel IoC container

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


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

为了深入研究我的问题,这里是我在 routes.php

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

这会生成一个视图,其中用户 select 将触发一个具体的导入。 select进行具体导入后,用户应获得个人视图以准备适当的导入(即上传 CSV 文件、select 要导入的区域等)

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

CMS框架部分包括:


回到我的 IoC 架构。在用户 select 编辑了具体的导入之后,以下路由将操作委托给自定义导入实现的控制器。如果它是一个支持标准操作的框架,比如通过 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 绑定 class:

$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);
});

------------

更新 1

为了在我的 IoC 绑定中访问路由,我的最新想法是这样工作的:

$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 使用路由器绑定的想法阻止了绑定和请求之间的直接耦合,这仍然感觉不对。使用路由器绑定将请求参数耦合到实现,听起来更合适。

我认为您已经对那里的 IoC 容器进行了过多的讨论。为什么不实现 Factory 模式并进行路由绑定,而不是创建多个控制器来处理不同的 Import?粗略示例如下:

  1. 创建路由绑定器 - 编辑您的 app/Provider/RouteServiceProvider.phpboot() 方法
public function boot(Router $router)
{
    parent::boot($router);

    // Add the statement below.
    $router->bind('import', 'App\RouteBindings@import');
}
  1. 创建 App\RouteBindings class 为 app/RouteBindings.php
  2. 使用以下内容创建 import() 方法:
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 class.
  2. 的路由

Route::get('import/{import}/{method}', 'ImportController@handleImport');

在这里,{import} 将根据您的 URL.return return 正确的 Import 具体 class

  1. 在您的 ImportControllerhandleImport() 中,您可以执行以下操作:
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 时,路由绑定将 return ImportAbc 的具体 class 并将其存储在 $importhandleImport() 方法,那么你的 handleImport() 方法将执行:$import->seed();。提示:您应该将其他控制器逻辑(例如 $import->status()->set() 移动到 Import class 中。保持你的控制器薄。

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

它有点像 Laravel 的路由模型绑定,只是您需要为绑定创建逻辑。

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