【问题标题】:Proper way to pass IoC container传递 IoC 容器的正确方法
【发布时间】:2015-09-10 20:31:41
【问题描述】:

我正在考虑DIIoC;现在使用Pimple。假设我在执行流程的早期定义了 IoC

$container = new Injection\Container();

$container['config'] = function ($c) {
    return new Config($c['loader']);
};

$container['request'] = function ($c) {
    return new Request($c['config']);
};

...

还有一个路由器类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
    }
}

我的问题是:

  1. IoC 传递给类的正确方法是什么(静态似乎是邪恶的......)或
  2. 是否甚至需要传递容器并且存在其他方法/概念?

阅读了一些不错的文章,但无法弄清楚

更新

我这样做的邪恶方法是定义另一个将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;
}

【问题讨论】:

    标签: php oop dependency-injection inversion-of-control


    【解决方案1】:

    IoC 有两种常用的模式,并且支持这两种模式。

    1. 依赖注入
    2. 服务定位器

    但是,似乎越来越多的 DI 胜过服务定位器模式。许多 DI 容器使使用 Service Locator 变得更加困难,并且在文档中警告不要走这条路。

    在 DI 中,您从不将容器传递给类,除非该类是 composition root 的一部分。组合根通过在应用程序的入口点解析对象图来启动一切,从那里开始,应用程序完全不知道 DI 容器(没有引用它)。请注意,此对象图可能包含创建类的运行时实例的Abstract Factories(通过注入从 DI 容器解析的函数或简单地更新它们)。

    服务定位器是另一端。通常,容器要么是静态的,要么作为唯一的依赖项传递给一个类。以这种方式构建类可能更容易,但是当您实际上必须配置 DI 容器时,您会付出代价。

    使用 DI,类的依赖关系是显式的,因此您无需进一步查看构造函数参数。使用服务定位器,配置依赖项要复杂得多。这就是近年来被认为是anti-pattern的主要原因(其实有很多原因)。

    因此,为了回答您的问题,如果您想采用现代方法来处理 IoC,请不要将 IoC 容器传递到应用程序中。相反,请在应用程序的入口点使用组合根来配置容器并构建对象图。

    依赖注入示例

    PHP 中的完整示例可见here。我在a discussion on the Pimple project找到了那个页面。

    所以,以你的例子为例:

    class Dog
    {
        public function woof($var1, $var2)
        {
            //$request = IoC service here
        }
    }
    

    你需要添加一个构造函数来接受你的 $request 服务,这样你的类就会收到它的一个实例。

    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 类完全不知道 DI 容器。

    【讨论】:

    • 谢谢并同意你的看法;还有“在 DI 中,你永远不会将容器传递给一个类,除非该类是组合根的一部分。”和“使用 DI,类的依赖关系是显式的,因此您无需进一步查看构造函数参数。”适用于我的情况。好的,但是如果类依赖于未知的服务呢?设置器注入?
    • What if the class depends on services that are yet unknown? - 使用 IoC 是针对抽象而不是具体类型进行编程。这在很多方面都有帮助,但主要是为了您可以为将来未知的具体类提供一种在不更改代码的情况下插入的方法。它还可以创建用于测试的模拟。只要类遵守抽象契约(通常是接口),这是可能的。我想提供一个sn-p,但是我对PHP不熟悉...
    • 谢谢,但这并不能解决问题,因为我使用的是call_user_func_array,并且只有传递的东西是url参数。
    • 想跳过 dog 服务的定义,因为它们位于 application 而不是 system 级别。
    • 很好的解释。不过,我有一个疑问。如果我的控制器 C 是由 IoC 容器创建的,并且将依赖项 D 注入到它的一个方法中,那么在此方法中,控制器需要调用 D 的方法,该方法又需要依赖项 @ 987654336@ 作为参数。 C如何将参数A传递给D->methodOfDWhichUsesA(/* A must be passed as parameter*/)。假设A 不是D 的重要依赖项,但它只能用于methodOfDWhichUsesAD 方法。如果不将容器传递给控制器​​C,您如何处理它?
    【解决方案2】:

    这里是我使用工厂、单例、依赖注入器、服务定位器、接口注入器和其他模式的一个例子。

    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;
    

    我一直在考虑写一个解释和我对这个主题的看法,但由于我不擅长写作,而且关于这些话题已经有太多的意见了(其中很大一部分是关于邪恶和反-一些东西),我将留下这个例子,它可能对某人有用。

    【讨论】:

      猜你喜欢
      • 2013-01-12
      • 2012-05-13
      • 2010-11-15
      • 1970-01-01
      • 2015-10-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多