Jua*_*Gon 21 php dependency-injection interface laravel laravel-5
我目前面临着一个非常有趣的架构和实现困境.
我有一个名为的接口ServiceInterface
,它有一个名为的方法execute()
然后我对这个接口有两个不同的实现:Service1
并且Service2
,它正确地实现了execute方法.
我有一个称为控制器MainController
,该控制器具有一个"类型的提示"为ServiceInterface
(依赖注入),这意味着两个,Service1
并且Service2
,可以被称为该依赖注入分辨率.
现在有趣的部分:
我不知道使用(Service1
或Service2
)使用哪些实现,因为我只知道我是否可以使用基于上一步的用户输入的一个或另一个.
这意味着用户选择一项服务,并根据该值我知道是否可以使用Service1
或Service2
.
我目前正在使用会话值解决依赖注入,所以根据值我返回一个实例或其他,但我真的认为这不是一个好方法.
如果你遇到类似的问题,请告诉我,你如何解决它,或者我能做些什么才能以正确的方式实现这一目标.
提前致谢.如果需要进一步的信息,请告诉我.
Jua*_*Gon 15
最后经过几天研究和思考有关这方面的最佳方法,使用Laravel我终于解决了.
我不得不说这是特别困难的Laravel 5.2
,因为在这个版本中,Session中间件只在路由中使用的控制器中执行,这意味着如果由于某种原因我使用了控制器(没有链接用于死记硬背)并尝试获得访问会话是不可能的.
所以,因为我不能使用会话我决定使用URL参数,这里你有解决方案的方法,我希望你们中的一些人发现它很有用.
所以,你有一个界面:
interface Service
{
public function execute();
}
Run Code Online (Sandbox Code Playgroud)
然后是接口的几个实现:
服务一:
class ServiceOne implements Service
{
public function execute()
{
.......
}
}
Run Code Online (Sandbox Code Playgroud)
服务二.
class ServiceTwo implements Service
{
public function execute()
{
.......
}
}
Run Code Online (Sandbox Code Playgroud)
现在有趣的部分:有一个控制器,其功能与服务接口有依赖性,但我需要根据使用输入将其解析为ServiceOne或ServiceTwo.所以:
控制器
class MyController extends Controller
{
public function index(Service $service, ServiceRequest $request)
{
$service->execute();
.......
}
}
Run Code Online (Sandbox Code Playgroud)
请注意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;
}
}
Run Code Online (Sandbox Code Playgroud)
所以现在所有的责任都是Resolver类,这个类基本上使用传递给contructor的参数来返回将用作Service接口实现的类的fullname(带命名空间):
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;
}
}
Run Code Online (Sandbox Code Playgroud)
好吧,我真的希望对你们这些人有所帮助.
最好的祝愿!
----------编辑-----------
我只是意识到,直接使用请求数据并不是一个好主意,在Laravel的容器内,从长远来看它确实会带来一些麻烦.
最好的方法是直接注册支持的所有可能实例(serviceone和servicetwo),然后直接从控制器或中间件解析其中一个,那么控制器"谁决定"使用什么服务(来自所有可用的)基于请求的输入.
最后它的工作原理相同,但它会让你以更自然的方式工作.
我要感谢rizqi.来自Laravel闲聊的问题频道的用户.
他亲自创建了一篇关于此的金色文章.请阅读它,因为完全以一种非常正确的方式解决了这个问题.
您定义控制器可以使用的事实ServiceInterface
是可以的
如果您必须根据上一步(据我所知,发生在上一个请求中)选择服务的具体实现,则将值存储在会话或数据库中也是正确的,因为您别无选择:选择你必须知道输入值的实现
重要的一点是在一个地方将具体实现的解析与输入值“隔离”:例如创建一个方法,将该值作为参数并从该值返回服务的具体实现:
public function getServiceImplementation($input_val)
{
switch($input_val)
{
case 1 : return new Service1();
case 2 : return new Service2();
}
}
Run Code Online (Sandbox Code Playgroud)
在你的控制器中:
public function controllerMethod()
{
//create and assign the service implementation
$this->service = ( new ServiceChooser() )->getServiceImplementation( Session::get('input_val') );
}
Run Code Online (Sandbox Code Playgroud)
在此示例中,我使用了不同的类来存储该方法,但您可以将该方法放置在控制器中或使用简单工厂模式,具体取决于应在应用程序中解析服务的位置