使用来自 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 要导入的区域等)
在我的概念中,导入实现包括:
- 控制器 class,用于执行特定的(操作)任务,例如上传 CSV 文件。它继承自cms框架的一个基本控制器
- 实现数据导入方式的导入 "business" 或 "service" class(并可能进一步委托给排队的作业等)
CMS框架部分包括:
- 用于所有 common/shared 导入任务的基本控制器 class,例如(开始准备好的导入,清理所有工作数据等)
- 基础服务class
ImportBase
,所有实现都继承自该服务。它提供了一个接口来接收任何导入的进度并实现共享操作,如清理工作数据等)
ImportStatus
class 是 ImportBase
-Class 的一部分,通过 $ImportBase->status()
处理所有运行时状态信息(如 "is the job still running, what is the progress). This class also provides a containter for a so called "payload”,允许任何具体的导入实现推送和获取自定义状态信息(即任何子流程已完成)
回到我的 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);
});
- 这种方法可以这样工作吗?
- 我能否以某种方式通过
$importKey
从我的路线到达 ServiceProvider(或者更好地从那里拉出它?)
- 是否有更好的解决方案来初始化我的具体导入实现?
------------
更新 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
?粗略示例如下:
- 创建路由绑定器 - 编辑您的
app/Provider/RouteServiceProvider.php
的 boot()
方法
public function boot(Router $router)
{
parent::boot($router);
// Add the statement below.
$router->bind('import', 'App\RouteBindings@import');
}
- 创建
App\RouteBindings
class 为 app/RouteBindings.php
- 使用以下内容创建
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.
}
}
- 创建用于解析
Import
class. 的路由
Route::get('import/{import}/{method}', 'ImportController@handleImport');
在这里,{import}
将根据您的 URL.return return 正确的 Import
具体 class
- 在您的
ImportController
的 handleImport()
中,您可以执行以下操作:
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 并将其存储在 $import
中 handleImport()
方法,那么你的 handleImport()
方法将执行:$import->seed();
。提示:您应该将其他控制器逻辑(例如 $import->status()->set()
移动到 Import
class 中。保持你的控制器薄。
只需确保您的 Import
class 具有相同的签名。
它有点像 Laravel 的路由模型绑定,只是您需要为绑定创建逻辑。
同样,这只是一个粗略的例子,但我希望它能有所帮助。
我需要一种根据请求参数通过 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 要导入的区域等)
在我的概念中,导入实现包括:
- 控制器 class,用于执行特定的(操作)任务,例如上传 CSV 文件。它继承自cms框架的一个基本控制器
- 实现数据导入方式的导入 "business" 或 "service" class(并可能进一步委托给排队的作业等)
CMS框架部分包括:
- 用于所有 common/shared 导入任务的基本控制器 class,例如(开始准备好的导入,清理所有工作数据等)
- 基础服务class
ImportBase
,所有实现都继承自该服务。它提供了一个接口来接收任何导入的进度并实现共享操作,如清理工作数据等) ImportStatus
class 是ImportBase
-Class 的一部分,通过$ImportBase->status()
处理所有运行时状态信息(如 "is the job still running, what is the progress). This class also provides a containter for a so called "payload”,允许任何具体的导入实现推送和获取自定义状态信息(即任何子流程已完成)
回到我的 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);
});
- 这种方法可以这样工作吗?
- 我能否以某种方式通过
$importKey
从我的路线到达 ServiceProvider(或者更好地从那里拉出它?) - 是否有更好的解决方案来初始化我的具体导入实现?
------------
更新 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
?粗略示例如下:
- 创建路由绑定器 - 编辑您的
app/Provider/RouteServiceProvider.php
的boot()
方法
public function boot(Router $router)
{
parent::boot($router);
// Add the statement below.
$router->bind('import', 'App\RouteBindings@import');
}
- 创建
App\RouteBindings
class 为app/RouteBindings.php
- 使用以下内容创建
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.
}
}
- 创建用于解析
Import
class. 的路由
Route::get('import/{import}/{method}', 'ImportController@handleImport');
在这里,{import}
将根据您的 URL.return return 正确的 Import
具体 class
- 在您的
ImportController
的handleImport()
中,您可以执行以下操作:
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 并将其存储在 $import
中 handleImport()
方法,那么你的 handleImport()
方法将执行:$import->seed();
。提示:您应该将其他控制器逻辑(例如 $import->status()->set()
移动到 Import
class 中。保持你的控制器薄。
只需确保您的 Import
class 具有相同的签名。
它有点像 Laravel 的路由模型绑定,只是您需要为绑定创建逻辑。
同样,这只是一个粗略的例子,但我希望它能有所帮助。