通过IoC容器的正确方法

时间:2015-09-10 20:31:42

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

我正在考虑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. 是否需要传递容器并存在其他方法/概念?
  3. 阅读一些不错的文章,但无法弄清楚

    更新

    我做的 evil 方式是定义另一个将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;
    }
    

2 个答案:

答案 0 :(得分:11)

IoC通常使用2种模式,两者都是支持者。

  1. 依赖注入
  2. 服务定位器
  3. 然而,似乎越来越多的DI赢得了服务定位器模式。许多DI容器使得使用Service Locator变得更加困难,并且在文档中发出警告,不要走这条路。

    在DI中,除非该类属于composition root,否则永远不会将容器传递给类。组合根通过在应用程序的入口点处解析对象图来启动所有运动,并且从那里应用程序完全不知道DI容器(没有引用它)。请注意,此对象图可能包含Abstract Factories,它们创建类的运行时实例(通过注入一个函数来从DI容器中解析,或者只是通过新增它们来实现)。

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

    使用DI,类的依赖关系是显式的,因此您不需要比构造函数参数更进一步。使用Service Locator,配置依赖项要复杂得多。这是主要原因(实际上有很多原因)为什么近年来它被认为是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容器。

答案 1 :(得分: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;

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