2016-04-26 105 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!