2016-04-26 18 views
9

Tôi hiện đang đối mặt với tình huống khó xử rất thú vị với kiến ​​trúc và triển khai của tôi.(Laravel) Chèn phụ thuộc động cho giao diện, dựa trên đầu vào của người dùng

Tôi có một giao diện được gọi ServiceInterface trong đó có một phương pháp gọi là execute()

Sau đó, tôi có hai triển khai khác nhau cho giao diện này: Service1Service2, mà thực hiện phương thức execute đúng cách.

Tôi có một bộ điều khiển gọi là MainController và bộ điều khiển này có một "kiểu gợi ý" cho ServiceInterface (dependency injection), nó có nghĩa là cả hai, Service1Service2, có thể được gọi là độ phân giải cho điều đó dependency injection.

Bây giờ phần thú vị:

Tôi không biết những người triển khai sử dụng (Service1 hoặc Service2) bởi vì tôi chỉ biết nếu tôi có thể sử dụng một hoặc khác dựa trên một người dùng nhập vào từ bước trước.

Điều đó có nghĩa là người dùng chọn dịch vụ và dựa trên giá trị đó tôi biết nếu có thể sử dụng Service1 hoặc Service2.

Tôi hiện đang giải quyết việc tiêm phụ thuộc bằng cách sử dụng giá trị phiên, do đó, tùy thuộc vào giá trị tôi trả về phiên bản hoặc trường hợp khác, NHƯNG tôi thực sự nghĩ rằng đó không phải là cách hay để thực hiện.

Hãy cho tôi biết nếu bạn đối mặt với điều gì đó tương tự và cách bạn giải quyết hoặc tôi có thể làm gì để đạt được điều này đúng cách.

Xin cảm ơn trước. Vui lòng cho tôi biết nếu cần thêm thông tin.

+0

Câu hỏi hay. – simhumileco

Trả lời

8

Cuối cùng sau một vài ngày nghiên cứu và suy nghĩ rất nhiều về cách tiếp cận tốt nhất cho điều này, sử dụng Laravel cuối cùng tôi đã giải quyết.

tôi phải nói rằng đây là đặc biệt khó khăn trong Laravel 5.2, bởi vì trong phiên bản này phiên middleware chỉ được thực hiện trong các bộ điều khiển được sử dụng trong một lộ trình, có nghĩa là nếu vì một lý do tôi đã sử dụng một bộ điều khiển (không liên kết cho một cuộc rote) và cố gắng để có được quyền truy cập vào phiên nó sẽ không thể được. Vì vậy, bởi vì tôi không thể sử dụng phiên, tôi quyết định sử dụng các tham số URL, ở đây bạn có cách tiếp cận giải pháp, tôi hy vọng một số bạn thấy nó hữu ích.

như vậy, bạn có một giao diện:

interface Service 
{ 
    public function execute(); 
} 

Sau đó, một vài hiện thực cho giao diện:

Các dịch vụ một:

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

Các dịch vụ hai.

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

Bây giờ là phần thú vị: có một bộ điều khiển với một chức năng mà có một sự phụ thuộc với giao diện dịch vụ NHƯNG tôi cần phải giải quyết nó dinamically để ServiceOne hoặc ServiceTwo có trụ sở tại một đầu vào sử dụng. Vì vậy:

Bộ điều khiển

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

Xin lưu ý rằng ServiceRequest, xác nhận rằng yêu cầu đã có các tham số mà chúng ta cần phải giải quyết phụ thuộc (gọi nó là 'service_name')

Bây giờ, trong AppServiceProvider chúng tôi có thể giải quyết sự phụ thuộc theo cách này:

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

Vì vậy, bây giờ tất cả các trách nhiệm là dành cho lớp Resolver, cl này ass cơ bản sử dụng tham số truyền cho các contructor để trả lại fullname (với không gian tên) của lớp có nghĩa là sẽ được sử dụng như một thực hiện của giao diện dịch vụ:

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

Vâng, tôi thực sự hy vọng nó giúp cho một số của bạn.

Lời chúc tốt đẹp nhất!

---------- CHỈNH SỬA -----------

Tôi chỉ nhận thấy rằng không nên sử dụng trực tiếp dữ liệu yêu cầu, bên trong container của Laravel, nó thực sự sẽ làm cho một số rắc rối trong dài hạn.

Cách tốt nhất là đăng ký trực tiếp tất cả các trường hợp có thể được hỗ trợ (serviceone và servicetwo) và sau đó giải quyết một trong số chúng trực tiếp từ bộ điều khiển hoặc phần mềm trung gian, sau đó là bộ điều khiển "ai quyết định" sử dụng dịch vụ nào (từ tất cả khả dụng) dựa trên dữ liệu đầu vào từ yêu cầu.

Cuối cùng nó hoạt động giống nhau, nhưng nó sẽ cho phép bạn làm việc một cách tự nhiên hơn.

Tôi phải nói cảm ơn đến rizqi. Người dùng từ kênh câu hỏi của cuộc trò chuyện chật chội của Laravel.

Cá nhân anh ta đã tạo một số vàng article về điều này. Vui lòng đọc nó vì giải quyết vấn đề này hoàn toàn và theo cách rất đúng.

laravel registry pattern

3

Thực tế là bạn xác định rằng điều khiển của bạn làm việc với ServiceInterface là ok

Nếu bạn phải chọn việc thực hiện cụ thể của căn cứ dịch vụ trên một bước trước (đó, như tôi đã hiểu, xảy ra trong một trước yêu cầu) lưu trữ giá trị trong phiên hoặc trong cơ sở dữ liệu cũng đúng, vì bạn không có lựa chọn thay thế: để chọn việc triển khai bạn phải biết giá trị của đầu vào

Điểm quan trọng là 'cô lập' độ phân giải của bê tông thực hiện từ giá trị đầu vào ở một nơi: ví dụ: tạo một phương thức lấy giá trị này làm tham số và trả về việc triển khai thực hiện cụ thể dịch vụ từ giá trị:

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

và trong điều khiển của bạn:

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

Trong ví dụ này tôi đã sử dụng một lớp khác để lưu trữ phương pháp, nhưng bạn có thể đặt phương pháp trong điều khiển hoặc sử dụng một máy đơn giản pattern, tùy thuộc vào nơi dịch vụ sẽ được giải quyết trong ứng dụng của bạn

+1

Cảm ơn vì điều đó. Trong thực tế, tôi chỉ muốn chắc chắn nếu sử dụng phiên là một cách tiếp cận đúng, tôi nghi ngờ vì truy cập vào phiên không dễ dàng từ thùng chứa Laravel (độ phân giải của phụ thuộc), nhưng tôi vừa phát hiện ra rằng trên thực tế tôi có thể sử dụng một Nhà máy cách tiếp cận để thực hiện điều đó theo cách "tự nhiên" hơn – JuanDMeGon

+0

@JuanDMeGon: bạn được chào đón – Moppo

1

tôi tìm ra cách tốt nhất để đối phó với điều này là sử dụng một mô hình nhà máy. Bạn có thể tạo một lớp nói là ServiceFactory và nó có một phương thức create() nó có thể chấp nhận một đối số được sử dụng để tự động chọn lớp bê tông nào để khởi tạo.

Trường có tuyên bố trường hợp dựa trên đối số.

Nó sẽ sử dụng App::make(ServiceOne::class) hoặc App::make(ServiceTwo::class). Tùy thuộc vào yêu cầu nào.

Sau đó, bạn có thể đưa điều này vào bộ điều khiển (hoặc dịch vụ phụ thuộc vào nhà máy).

Sau đó, bạn có thể thử nó trong thử nghiệm đơn vị dịch vụ.

+0

Có, đó là cách tốt nhất để thực hiện. Tôi đã sử dụng cách tiếp cận tương tự, sau bài viết được chia sẻ trên phản hồi ban đầu: http://rizqi.id/laravel-registry-pattern – JuanDMeGon

1

Đây là một vấn đề thú vị. Tôi hiện đang sử dụng Laravel 5.5 và đã nghiền ngẫm nó. Tôi cũng muốn nhà cung cấp dịch vụ của tôi trả về một lớp cụ thể (thực hiện một giao diện) dựa trên đầu vào của người dùng. Tôi nghĩ rằng nó tốt hơn để tự chuyển các đầu vào từ bộ điều khiển để dễ dàng hơn để xem những gì đang xảy ra. Tôi cũng sẽ lưu trữ các giá trị có thể có của tên lớp trong cấu hình. Vì vậy, dựa trên các lớp dịch vụ và giao diện mà bạn đã định nghĩa ở trên tôi đã đưa ra với điều này:

/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) 
     ] 
    ]; 
} 

/app/Pr 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; 
      } 
     ); 
    } 
} 

Bằng cách này chúng ta có thể xác nhận các đầu vào để đảm bảo chúng tôi đi qua một dịch vụ hợp lệ, sau đó bộ điều khiển xử lý thông qua các đầu vào từ đối tượng Request vào ServiceProvider. Tôi chỉ nghĩ rằng khi nói đến việc duy trì mã này, nó sẽ được rõ ràng những gì đang xảy ra như trái ngược với việc sử dụng các đối tượng yêu cầu trực tiếp trong ServiceProvider. PS Hãy nhớ đăng ký CustomServiceProvider!

Các vấn đề liên quan