使用Laravel IoC容器中的参数加载对象 [英] Load objects with parameters from Laravel IoC container

查看:71
本文介绍了使用Laravel IoC容器中的参数加载对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要一种根据请求参数通过IoC提供程序加载对象的方法.现在,我直接使用要重构的App::make(xy, [$urlParamter]加载对象,以便可以按预期方式使用依赖项注入.为了描述我当前的体系结构,我需要向您展示一些信息,最后您会发现我对此有具体疑问.

I need a way to load objects via IoC provider depending on a request parameter. Right now I'm loading my objects directly with App::make(xy, [$urlParamter] which I want to refactor, so that I can use dependency injection the way it is supposed to. To describe my current architecture I need to show you quiet some information and in the end you find my concrete questions I have about it.

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

I'm building up a general CMS framework which provides an import architecture that is extendable with a custom import implementation. Now I'm struggling with properly loading the concrete classes via IoC container, because they always depend on the selected import.

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

To dig into my problem, here is my entry point in routes.php

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

这将生成一个视图,用户可以在其中选择要触发的具体导入.选择具体的导入后,用户应获得单个视图以准备适当的导入(即,上传CSV文件,选择要导入的区域等)

This generates a view where the user selects a concrete import to be triggered. After selecting a concrete import, the user should get individual views to prepare the appropriate import (i.e. Upload a CSV file, select an area to import, etc.)

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

In my concept an import implementation consist of:

  • 一个控制器类,用于实现特定的(操作)任务,例如上载CSV文件.它继承自cms框架的基本控制器
  • 导入业务"或服务"类,用于实现如何导入数据(并可能进一步委派给排队的作业等)

CMS框架部分包括:

The CMS framework part consists of:

  • 用于所有常见/共享导入任务的基本控制器类,例如(开始准备的导入,清除所有工作数据等)
  • 所有实现都继承自的基本服务类ImportBase.它提供了一个界面来接收任何导入的进度,并实现了共享操作,例如清理工作数据等.)
  • 一个ImportStatus类,它是ImportBase类的一部分,通过$ImportBase->status()来处理所有运行时状态信息(例如作业仍在运行,进度如何).该类还提供了一个容器,用于所谓的有效负载",允许任何具体的导入实现推送和获取自定义状态信息(即,任何子过程都已完成)
  • A base controller class for all common/shared import tasks like (start the prepared import, clean all working data, etc.)
  • A base service class ImportBase where all implementations inherit from. It provides an interface to receive a progress for any import and implements shared operations like cleaning up working data, etc.)
  • An ImportStatus class which is part of the ImportBase-Class via $ImportBase->status() to handle all runtime status informations (like "is the job still running, what is the progress). This class also provides a containter for a so called "payload" that allows any conrete import implementation to push and fetch custom status informations (ie. any sub-process has been finished)

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

So back to my IoC architecture. After the user selected a concrete import, the following route delegates the action to the custom import implementation's controller. If it's a framework supported standard-action like via URL /import/<importkey>/clean, the inherited BaseController of the cms framework takes over and handles the request

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

我知道可以改善通过命名约定进行的直接绑定(也许可以通过自定义配置文件),但是现在这对我有效.

I know that this direct binding via a naming convention can be improved (maybe via a custom configuration file), but for now this works for me.

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

Now I need to show an example of how I tried to implement a concrete import target in my controller via /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>');
}

我对Import类的IoC绑定:

My IoC binding for the Import 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对象中的导入状态的延迟加载看起来像这样

And finally, the lazy loading of the import status, which is encapsulated in the ImportStatus object looks like this

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

    return $this->status;
}

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

I hope that demonstrates the way I try to resolve my import objects from the IoC container.

到目前为止,我的学习是,这不是注入对象的正确方法.

My learning so far is, that this is not the right way to inject my objects.

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

Is the assumption right, that I should not pass the $importKey at runtime to the App::make() and rather should try to make this independ?

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

My failed attempt on this was to make the IoC binding smarter and let it access the Request to properly inject my concrete import object with the required $importKey, like (pseudo code!):

$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(或者最好从那里拉它吗?)
  • 是否有更好的解决方案来初始化我的具体导入实现?
    • Does this approach can work like this?
    • Can I somehow pass through the $importKey from my route to the ServiceProvider (or better pull it from there?)
    • Is there a better solution to initialize my concrete import implementations?
    • 更新1

      为了让我最新的想法在IoC绑定中访问路由,我采用了这种工作方式:

      For my lattest idea to access the Route in my IoC Binding, I got this way working:

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

      Nevertheless the idea of @Sandyandi N. dela Cruz to use a router binding prevents the direct coupling between the Binding and the Request which still doesn't feel right. Using router-binding to couple a request parameter to an implementation, sounds more appropriate.

      推荐答案

      我认为您在这里的IoC容器上住了很多.为什么不实现Factory模式并进行路由绑定,而不是创建多个控制器来处理不同的Import?粗略示例如下:

      I think you've dwelt to much on the IoC container there. Why not implement the Factory pattern and do a route binding instead of creating multiple controllers to handle different Imports? Crude example as follows:

      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类创建为app/RouteBindings.php
      2. 使用以下方法创建import()方法:
      1. Create the App\RouteBindings class as app/RouteBindings.php
      2. Create an import() method with the following:

      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类的路线.
      1. Create a route for resolving an Import class.

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

      在这里,{import}将根据您的URL返回正确的Import具体类.

      Here, {import} will return the proper Import concrete class based on your URL.

      1. 在您的ImportControllerhandleImport()中,您可以执行以下操作:
      1. In your ImportController's handleImport() you can do the following:

      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并将其存储在handleImport()方法的$import中,然后您的handleImport()方法将执行:$import->seed(); .提示:您可能应该将其他控制器逻辑,例如$import->status()->set()移到Import类中.保持您的控制器薄.

      So when you hit: http://example.com/import/import_abc/seed, the route binding will return a concrete class of ImportAbc and store it in $import on your handleImport() method, then your handleImport() method will execute: $import->seed();. Tip: you should probably move other controller logic such as $import->status()->set() into the Import class. Keep your controllers thin.

      只需确保您的Import类具有相同的签名即可.

      Just make sure your Import classes have the same signature.

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

      It's kinda like Laravel's Route Model Binding except you create the logic for the bindings.

      再次,这只是一个粗略的例子,但我希望它会有所帮助.

      Again, this is just a crude example but I hope it helps.

      这篇关于使用Laravel IoC容器中的参数加载对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆