传递 IoC 容器的正确方法
Proper way to pass IoC container
我正在思考 DI
和 IoC
;现在使用 Pimple。可以说我在执行流程的早期定义了我的 IoC
$container = new Injection\Container();
$container['config'] = function ($c) {
return new Config($c['loader']);
};
$container['request'] = function ($c) {
return new Request($c['config']);
};
...
还有一个路由器 class call_user_func_array
//$class = 'Dog', $method = 'woof', $this->args = ['foo', 'bar']
call_user_func_array(array(new $class, $method), $this->args);
所以新对象在没有意识到 IoC
的情况下被实例化,但我仍然想重用定义的一些服务。
class Dog
{
public function woof($var1, $var2)
{
//$request = IoC service here
}
}
我的问题是:
- 将
IoC
传递给 class 的正确方法是什么(静态似乎是邪恶的...)或
- 甚至有必要传递容器并且存在其他 methods/concepts 吗?
读了一些不错的文章,但是没弄明白
- Understanding
IoC
Containers and Dependency Injection
- Why not pass your IoC container around?
更新
我这样做的邪恶方式是定义另一个服务,将IoC
保存在静态属性
$container['services'] = function ($c) {
return Services::create($c); //make the service
};
$container['services']; //call the service
稍后在
中访问它
class Dog
{
public function woof($var1, $var2)
{
$services = new Services();
$request = $services['request']; //retrieving the request service
}
}
更新 2
决定用危害最小的方式
//passing the container here
call_user_func_array(array(new $class($container), $method), $this->args);
并将参数存入__constructor
public $container;
public function __construct(Injection\Container $container)
{
$this->container = $container;
}
有两种 IoC 常用的模式,以及两种模式的支持者。
- 依赖注入
- 服务定位器
但是,似乎越来越多的 DI 胜过服务定位器模式。许多 DI 容器使使用服务定位器变得更加困难,并且在文档中有警告不要走那条路。
在 DI 中,您 永远不会 将容器传递给 class,除非 class 是创建运行时实例的 composition root. The composition root starts everything in motion by resolving the object graph at the entry point of the application, and from there on the application is entirely unaware of the DI container (has no reference to it). Note that this object graph may contain Abstract Factories 的一部分classes(通过注入函数从 DI 容器解析或简单地更新它们)。
服务定位器是频谱的另一端。通常,容器要么是静态的,要么作为唯一的依赖项传递给 class。以这种方式构建 classes 可能更容易,但是当您实际必须 配置 DI 容器时,您稍后会付出代价。
使用 DI,class 的依赖关系是显式的,因此您无需查看构造函数参数。使用服务定位器,配置依赖项要复杂得多。这也是近年来被认为是anti-pattern.
的主要原因(其实有很多原因)
因此,为了回答您的问题,如果您想遵循 IoC 的现代方法,请不要将 IoC 容器传递到应用程序中。相反,在应用程序的入口点使用组合根来配置容器并构建对象图。
依赖注入示例
PHP中有一个完整的例子可以看here. I found that page in a discussion on the Pimple project。
因此,根据您的示例:
class Dog
{
public function woof($var1, $var2)
{
//$request = IoC service here
}
}
您需要添加一个构造函数来接受您的 $request 服务,这样您的 class 就会收到它的一个实例。
class Dog
{
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function woof($var1, $var2)
{
//Use $request here, disregard the IoC container
$this->request->doSomething()
}
}
然后您将有一个部分在您的组合根中定义控制器。这是注入依赖项的地方。
$container = new Injection\Container();
$container['config'] = function ($c) {
return new Config($c['loader']);
};
$container['request'] = function ($c) {
return new Request($c['config']);
};
$container['dog'] = $container->factory(function ($c) {
return new Dog($c['request']);
});
$container['user_controller'] = $container->share(function ($container) {
return new UserController(
$container['dog'] //,
// $container['someOtherDependency']
);
});
如您所见,使用这种方法,您的 Dog
class 完全不知道 DI 容器。
这里是我玩弄工厂、单例、依赖注入器、服务定位器、接口注入器和其他模式的一个例子。
config.php
return [
'services' => [
'request' => 'Request',
'view' => 'View',
'db' => function() {
return new DB( 'host', 'username', 'password', 'dbname' );
},
'translator' => function( $sm, $config ) {
return new Translator( $sm->db, $config );
},
'model' => function() {
return new ModelFactory( $sm );
}
],
'interfaces' => [
'iService' => function( $object, $sm ) {
$object->sm = $sm;
}
],
'translator' => [
'locale' => 'en_US',
]
];
服务容器
class ServiceManager {
private $services, $interfaces, $params;
function __construct( $config )
{
foreach( $config[ 'services' ] as $name => $value )
$this->add( $name, $value, isset( $config[ $name ] ) ? $config[ $name ] : null );
$this->interfaces = isset( $config[ 'interfaces' ] ) ? $config[ 'interfaces' ] : null;
}
function add( $name, $service, $params = null )
{
$this->services[ $name ] = $service;
$this->params[ $name ] = $params;
}
function __get( $name )
{
if ( is_string( $this->services[ $name ] ) )
$this->services[ $name ] = new $this->services[ $name ]( $this->params[ $name ] );
if ( is_callable( $this->services[ $name ] ) )
$this->services[ $name ] = $this->services[ $name ]( $this, $this->params[ $name ] );
foreach( $this->interfaces as $interface => $value ) {
if ( $this->services[ $name ] instanceof $interface )
$value( $this->services[ $name ], $this );
}
return $this->services[ $name ];
}
}
例子
interface iService {}
class View implements iService {
function render() {
print_r( $this->sm );
}
};
class DB {
function __construct( $host, $username, $password, $dbname ) {}
};
class Translator {
function __construct( $db, $config ) {}
};
class ModelFactory {
function __construct( $sm ) {}
}
class App {
function __construct() {
$sm = new ServiceManager( include 'config.php' );
$sm->view->render();
}
}
new App;
我一直在考虑写一篇关于这个主题的解释和我的看法,但由于我不擅长写作,而且围绕这些主题已经有太多的意见(其中很大一部分是关于邪恶和anti-something ),我就留下这个例子,它可能对某人有用。
我正在思考 DI
和 IoC
;现在使用 Pimple。可以说我在执行流程的早期定义了我的 IoC
$container = new Injection\Container();
$container['config'] = function ($c) {
return new Config($c['loader']);
};
$container['request'] = function ($c) {
return new Request($c['config']);
};
...
还有一个路由器 class call_user_func_array
//$class = 'Dog', $method = 'woof', $this->args = ['foo', 'bar']
call_user_func_array(array(new $class, $method), $this->args);
所以新对象在没有意识到 IoC
的情况下被实例化,但我仍然想重用定义的一些服务。
class Dog
{
public function woof($var1, $var2)
{
//$request = IoC service here
}
}
我的问题是:
- 将
IoC
传递给 class 的正确方法是什么(静态似乎是邪恶的...)或 - 甚至有必要传递容器并且存在其他 methods/concepts 吗?
读了一些不错的文章,但是没弄明白
- Understanding
IoC
Containers and Dependency Injection - Why not pass your IoC container around?
更新
我这样做的邪恶方式是定义另一个服务,将IoC
保存在静态属性
$container['services'] = function ($c) {
return Services::create($c); //make the service
};
$container['services']; //call the service
稍后在
中访问它class Dog
{
public function woof($var1, $var2)
{
$services = new Services();
$request = $services['request']; //retrieving the request service
}
}
更新 2
决定用危害最小的方式
//passing the container here
call_user_func_array(array(new $class($container), $method), $this->args);
并将参数存入__constructor
public $container;
public function __construct(Injection\Container $container)
{
$this->container = $container;
}
有两种 IoC 常用的模式,以及两种模式的支持者。
- 依赖注入
- 服务定位器
但是,似乎越来越多的 DI 胜过服务定位器模式。许多 DI 容器使使用服务定位器变得更加困难,并且在文档中有警告不要走那条路。
在 DI 中,您 永远不会 将容器传递给 class,除非 class 是创建运行时实例的 composition root. The composition root starts everything in motion by resolving the object graph at the entry point of the application, and from there on the application is entirely unaware of the DI container (has no reference to it). Note that this object graph may contain Abstract Factories 的一部分classes(通过注入函数从 DI 容器解析或简单地更新它们)。
服务定位器是频谱的另一端。通常,容器要么是静态的,要么作为唯一的依赖项传递给 class。以这种方式构建 classes 可能更容易,但是当您实际必须 配置 DI 容器时,您稍后会付出代价。
使用 DI,class 的依赖关系是显式的,因此您无需查看构造函数参数。使用服务定位器,配置依赖项要复杂得多。这也是近年来被认为是anti-pattern.
的主要原因(其实有很多原因)因此,为了回答您的问题,如果您想遵循 IoC 的现代方法,请不要将 IoC 容器传递到应用程序中。相反,在应用程序的入口点使用组合根来配置容器并构建对象图。
依赖注入示例
PHP中有一个完整的例子可以看here. I found that page in a discussion on the Pimple project。
因此,根据您的示例:
class Dog
{
public function woof($var1, $var2)
{
//$request = IoC service here
}
}
您需要添加一个构造函数来接受您的 $request 服务,这样您的 class 就会收到它的一个实例。
class Dog
{
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function woof($var1, $var2)
{
//Use $request here, disregard the IoC container
$this->request->doSomething()
}
}
然后您将有一个部分在您的组合根中定义控制器。这是注入依赖项的地方。
$container = new Injection\Container();
$container['config'] = function ($c) {
return new Config($c['loader']);
};
$container['request'] = function ($c) {
return new Request($c['config']);
};
$container['dog'] = $container->factory(function ($c) {
return new Dog($c['request']);
});
$container['user_controller'] = $container->share(function ($container) {
return new UserController(
$container['dog'] //,
// $container['someOtherDependency']
);
});
如您所见,使用这种方法,您的 Dog
class 完全不知道 DI 容器。
这里是我玩弄工厂、单例、依赖注入器、服务定位器、接口注入器和其他模式的一个例子。
config.php
return [
'services' => [
'request' => 'Request',
'view' => 'View',
'db' => function() {
return new DB( 'host', 'username', 'password', 'dbname' );
},
'translator' => function( $sm, $config ) {
return new Translator( $sm->db, $config );
},
'model' => function() {
return new ModelFactory( $sm );
}
],
'interfaces' => [
'iService' => function( $object, $sm ) {
$object->sm = $sm;
}
],
'translator' => [
'locale' => 'en_US',
]
];
服务容器
class ServiceManager {
private $services, $interfaces, $params;
function __construct( $config )
{
foreach( $config[ 'services' ] as $name => $value )
$this->add( $name, $value, isset( $config[ $name ] ) ? $config[ $name ] : null );
$this->interfaces = isset( $config[ 'interfaces' ] ) ? $config[ 'interfaces' ] : null;
}
function add( $name, $service, $params = null )
{
$this->services[ $name ] = $service;
$this->params[ $name ] = $params;
}
function __get( $name )
{
if ( is_string( $this->services[ $name ] ) )
$this->services[ $name ] = new $this->services[ $name ]( $this->params[ $name ] );
if ( is_callable( $this->services[ $name ] ) )
$this->services[ $name ] = $this->services[ $name ]( $this, $this->params[ $name ] );
foreach( $this->interfaces as $interface => $value ) {
if ( $this->services[ $name ] instanceof $interface )
$value( $this->services[ $name ], $this );
}
return $this->services[ $name ];
}
}
例子
interface iService {}
class View implements iService {
function render() {
print_r( $this->sm );
}
};
class DB {
function __construct( $host, $username, $password, $dbname ) {}
};
class Translator {
function __construct( $db, $config ) {}
};
class ModelFactory {
function __construct( $sm ) {}
}
class App {
function __construct() {
$sm = new ServiceManager( include 'config.php' );
$sm->view->render();
}
}
new App;
我一直在考虑写一篇关于这个主题的解释和我的看法,但由于我不擅长写作,而且围绕这些主题已经有太多的意见(其中很大一部分是关于邪恶和anti-something ),我就留下这个例子,它可能对某人有用。