2016-04-26 67 views
9

我目前正面临着与我的体系结构和实现一个非常有趣的两难境地。(Laravel)动态依赖注入的界面,根据用户输入

我有称为ServiceInterface其具有称为​​

方法然后,我必须为这个接口两种不同的实现的接口:Service1Service2,其适当地实现了执行方法。

我有一个名为MainController控制器和该控制器具有一个“类型的提示”为ServiceInterface依赖注入),这意味着这两者Service1Service2,可以称为该依赖注入分辨率。

现在最有趣的部分:

我不知道用哪个这些实现的(Service1Service2),因为我只知道如果我能基于从用户输入使用一个或其他前一步。

这意味着用户选择一项服务,并根据该值我知道如果可以使用Service1Service2

我目前解决使用会话值的依赖注入,所以根据我返回一个实例或其他的价值,但我真的认为这是不这样做的好方法。

请让我知道,如果你遇到类似的东西,你怎么解决这个问题,或者我能做些什么以正确的方式来实现这一目标。

在此先感谢。请让我知道是否需要进一步的信息。

+0

一个很好的问题。 – simhumileco

回答

8

数天后,研究和思考了很多关于这个最好的方法,使用Laravel我终于解决了最后解决。

我不得不说,这是特别困难的Laravel 5.2,因为在这个版本中会议中间件仅在路由使用的控制器上执行,这意味着,如果由于某种原因,我用了一个控制器(不链接对于死记硬背),并尝试访问会议,这是不可能的。

所以,因为我不能使用会话,我决定使用URL参数,在这里你有解决方案,我希望你们中的一些人认为它有用。

所以,你有一个接口:

interface Service 
{ 
    public function execute(); 
} 

然后一对夫妇的接口实现:

服务之一:

class ServiceOne implements Service 
{ 
    public function execute() 
    { 
     ....... 
    } 
} 

服务两项。

class ServiceTwo implements Service 
{ 
    public function execute() 
    { 
     ....... 
    } 
} 

现在最有趣的部分:有一个包含了与服务接口的相关性功能的控制器,但我需要dinamically解决它基于在使用输入ServiceOne或ServiceTwo。所以:

控制器

class MyController extends Controller 
{ 
    public function index(Service $service, ServiceRequest $request) 
    { 
     $service->execute(); 
     ....... 
    } 
} 

请注意,ServiceRequest,验证了请求中已经存在,我们需要解决的依赖参数(称之为'service_name'

现在,在AppServiceProvider我们可以通过这种方式解决依赖关系:

class AppServiceProvider extends ServiceProvider 
{ 
    public function boot() 
    { 

    } 

    public function register() 
    { 
     //This specific dependency is going to be resolved only if 
     //the request has the service_name field stablished 
     if(Request::has('service_name')) 
     { 
      //Obtaining the name of the service to be used (class name) 
      $className = $this->resolveClassName(Request::get('service_name'))); 

      $this->app->bind('Including\The\Namespace\For\Service', $className); 
     } 
    } 

    protected function resolveClassName($className) 
    { 
     $resolver = new Resolver($className); 
     $className = $resolver->resolveDependencyName(); 
     return $className; 
    } 
} 

所以现在所有的责任都是针对Resolver类的,这个cl屁股基本上使用传递给构造器的参数,那将被用作服务接口的实现类的返回全名(命名空间):

class Resolver 
{ 
    protected $name; 
    public function __construct($className) 
    { 
     $this->name = $className; 
    } 

    public function resolveDependencyName() 
    { 
     //This is just an example, you can use whatever as 'service_one' 
     if($this->name === 'service_one') 
     { 
      return Full\Namespace\For\Class\Implementation\ServiceOne::class; 
     } 

     if($this->name === 'service_two') 
     { 
      return Full\Namespace\For\Class\Implementation\ServiceTwo::class; 
     } 
     //If none, so whrow an exception because the dependency can not be resolved 
     throw new ResolverException; 
    } 
} 

嗯,我真的希望这有助于一些你的。

祝好!

---------- -----------编辑

我才意识到,这不是直接使用请求数据,里面是个好主意Laravel的集装箱,从长远来看真的会有一些麻烦。

最好的方法是直接注册所有可能的实例(serviceone和servicetwo),然后直接从控制器或中间件中解析其中的一个,然后是控制器“谁决定”使用哪种服务(从所有可用的)基于来自请求的输入。

最后它的工作原理是一样的,但它可以让你以更自然的方式工作。我不得不说,感谢rizqi。来自Laravel闲聊的问题频道的用户。

他亲自为此创建了一个黄金article。请阅读它,因为完全和正确地解决这个问题。

laravel registry pattern

3

您定义您的控制器可与ServiceInterface的事实是确定

如果你要选择在前面的步骤(即,正如我所了解,发生在先前的具体实施服务筑底的请求)将值存储在会话或数据库中也是正确的,因为您别无选择:要选择实现,您必须知道输入的值

重要的一点是“隔离”混凝土的分辨率从一个地方的输入值实现:例如,创建一个方法,该方法将此值作为参数,并从该地址返回服务的具体实现值:

public function getServiceImplementation($input_val) 
{ 
    switch($input_val) 
    { 
     case 1 : return new Service1(); 
     case 2 : return new Service2(); 
    }  
} 

和控制器:

public function controllerMethod() 
{ 
    //create and assign the service implementation 
    $this->service = (new ServiceChooser())->getServiceImplementation(Session::get('input_val')); 
} 

在这个例子中,我使用了不同的类来存储的方法,但你可以将方法在控制器或使用简单工厂模式,具体情况取决于服务应该在你的应用程序

+1

谢谢。实际上,我只是想确定如果使用会话是正确的方法,我怀疑是因为从Laravel容器(依赖关系的解析)访问会话并不容易,但是我刚刚发现,实际上我可以使用Factory以更“自然”的方式执行该方法 – JuanDMeGon

+0

@JuanDMeGon:不客气 – Moppo

1

我找到对付这是在使用工厂模式的最佳途径。你可以创建一个类,如ServiceFactory,它有一个方法create()它可以接受一个参数,用于动态选择要实例化的具体类。

它有基于论证的案例陈述。

它将使用App::make(ServiceOne::class)App::make(ServiceTwo::class)。取决于需要哪一个。

然后您可以将其注入到您的控制器(或取决于工厂的服务)中。

然后,您可以在服务单元测试中对其进行模拟。

+0

是的,这是最好的方式。我使用了与原始响应共享的文章相同的方法:http://rizqi.id/laravel-registry-pattern – JuanDMeGon

1

这是一个有趣的问题。我目前正在使用Laravel 5.5,并一直在仔细研究。我也希望我的服务提供者根据用户输入返回一个特定的类(实现一个接口)。我认为手动传递来自控制器的输入更好,因此更容易看到发生了什么。我还会在配置中存储类名称的可能值。 所以根据你所定义的服务类和接口上面,我本想出了:

/config/services.php

return [ 
    'classes': [ 
     'service1' => 'Service1', 
     'service2' => 'Service2', 
    ] 
] 

/app/Http/Controllers/MainController.php

public function index(ServiceRequest $request) 
{ 
    $service = app()->makeWith(ServiceInterface::class, ['service'=>$request->get('service)]); 
    // ... do something with your service 
} 

/app/Http/Requests/ServiceRequest.php

public function rules(): array 
    $availableServices = array_keys(config('services.classes')); 
    return [ 
     'service' => [ 
      'required', 
      Rule::in($availableServices) 
     ] 
    ]; 
} 

/应用/镨oviders/CustomServiceProvider.php

class CustomServiceProvider extends ServiceProvider 
{ 
    public function boot() {} 

    public function register() 
    { 
     // Parameters are passed from the controller action 
     $this->app->bind(
      ServiceInterface::class, 
      function($app, $parameters) { 
       $serviceConfigKey = $parameters['service']; 
       $className = '\\App\\Services\\' . config('services.classes.' . $serviceConfigKey); 
       return new $className; 
      } 
     ); 
    } 
} 

这样我们就可以验证输入,以确保我们通过一个有效的服务,则控制器使处理从请求对象的输入到的ServiceProvider。我只是想,当涉及到维护这些代码时,将清楚发生了什么,而不是直接在ServiceProvider中使用请求对象。 PS记得注册CustomServiceProvider!