The Aura Project for PHP

aura.di

简介
实例化Manager
设置服务
变体二:延迟加载
变体三:构造器参数
变体四:类构造器参数
变体四…:覆盖类构造器参数
获取服务
继承构造器参数
工厂和依赖实践
setter注入
子容器
总结

简介

Aura DI包提供了依赖注入的容器系统,有以下特性:

当与工厂类一起使用时,你可以分开进行对象配置,对象构造和对象使用,为开发者提供更大的灵活性和更好的可测试性。虽然想更充分地介绍依赖注入的性质和好处,但超出了本文档的范围。如需更详细地了解“控制反转”和“依赖注入”,请参考Martin Fowler写的文章http://martinfowler.com/articles/injection.html


实例化Manager

Aura DI包内部存在一个返回新DI实例的instance脚本:

<?php
$di = require '/path/to/aura.di/scripts/instance.php';

另外,你也可以把Aura DI的'src/'目录添加到自动加载器中,然后使用下面方法实例化:

<?php
use aura\di\Manager;
use aura\di\Forge;
use aura\di\Config;
$di = new Manager(new Forge(new Config));

Manager就是DI容器本身。其支撑对象有:

我们不需要直接操作支撑对象;而是通过Manager的方法来使用它们。


设置服务

我们将在下面的示例中设置一个数据库连接的服务。数据库连接类定义如下:

<?php
namespace example\package;

class Database
{
    public function __construct($hostname, $username, $password)
    {
        // ... make the database connection
    }
}

我们将分四个步骤来讲解DI容器的用法,从最简单的服务创建到更复杂的应用。每种变体都是正确的用法,也都反映了DI容器的优缺点。

变体一:贪婪加载

在这种变种中,我们使用new操作符实例化对象,并创建服务。

<?php
$di->set('database', new \example\package\Database(
    'localhost', 'user', 'passwd'
));

这种方法在设置服务的时候就创建了数据库对象。无论我们将来是否要从容器取回对象,它总是被创建的。

变体二:延迟加载

在这种变体中,我们在闭包中创建服务,仍然使用new操作符。

<?php
$di->set('database', function() {
    return new \example\package\Database('localhost', 'user', 'passwd');
});

当我们使用$di->get('database')从容器中取回服务时才会创建数据库对象。在闭包中实例化对象可以实现延迟加载数据库对象;如果我们不调用$di->get('database')方法取回对象,那么该对象永远不会被创建。

变体三:构造器参数

在这种变体中,我们将使用$di->newInstance()方法代替new操作符。为了达到延迟加载的目的,同样在闭包中实例化对象。

<?php
$di->set('database', function() use ($di) {
    return $di->newInstance('example\package\Database', array(
        'hostname' => 'localhost',
        'username' => 'user',
        'password' => 'passwd',
    ));
});

newInstance()方法使用Forge对象反射到被实例化类的构造函数上。然后传递关联数组作为类构造器参数。数组中元素的顺序无关紧要,缺失的参数将使用类构造器定义的默认值。

变体四:类构造器参数

在这种变体中,我们将分开处理Database类的配置过程和延迟实例化过程。

<?php
$di->params['example\package\Database'] = array(
    'hostname' => 'localhost',
    'username' => 'user',
    'password' => 'passwd',
);

$di->set('database', function() use ($di) {
    return $di->newInstance('example\package\Database');
});

作为对象创建的一部分,Forge对象检查传递的$di->params参数值。这些值在类实例化时将会和构造器默认参数合并,并把合并后的结果传递给构造器(同样,数组中元素顺序是无关紧要的,键名称和构造器参数名匹配就行)。

到目前为止,我们已经成功地把类配置从类实例化过程分离出来,并且能够从容器中延迟加载服务对象。

变体四…:覆盖类构造器参数

在这种变体中,我们在实例化对象的时候覆盖$di->params参数的值。

<?php
$di->params['example\package\Database'] = array(
    'hostname' => 'localhost',
    'username' => 'user',
    'password' => 'passwd',
);

$di->set('database', function() use ($di) {
    return $di->newInstance('example\package\Database', array(
        'hostname' => 'example.com',
    ));
});

实例化时传递的参数优先级高于配置参数值,而配置参数值的优先级又高于类构造器默认的参数值。


获取服务

要从容器中取回服务对象,调用$di->get()方法即可。

<src > <?php $db = $di->get('database'); </src>

继承构造器参数

在下面的示例中,我们将会增加一个抽象类Model以及两个具体类BlogModelWikiModel。主要想法是:所有的Model类要操作数据库表都需要一个Database连接类。

<?php
namespace example\package;

abstract class Model
{
    protected $db;

    public function __construct(Database $db)
    {
        $this->db = $db;
    }
}

class BlogModel extends Model
{
    // ...
}

class WikiModel extends Model
{
    // ...
}

我们将会为BlogModelWikiModel创建服务,并把数据库服务注入到以上两上Model中作为服务定义一部分。

<?php
// default config for the Database class
$di->params['example\package\Database'] = array(
    'hostname' => 'localhost',
    'username' => 'user',
    'password' => 'passwd',
);

// a database service
$di->set('database', function() use ($di) {
    return $di->newInstance('example\package\Database');
});

// a blog-model service
$di->set('blog_model', function() use ($di) {
    return $di->newInstance('example\package\BlogModel', array(
        'db' => $di->get('database'),
    ));
});

// a wiki-model service
$di->set('wiki_model', function() use ($di) {
    return $di->newInstance('example\package\WikiModel', array(
        'db' => $di->get('database'),
    ));
});

但是这里如果使用DI容器提供的配置可继承性,我们可以通过类配置来定义数据库服务注入。

<?php
// default params for the Database class
$di->params['example\package\Database'] = array(
    'hostname' => 'localhost',
    'username' => 'user',
    'password' => 'passwd',
);

// default params for the Model class
$di->params['example\package\Model'] = array(
    'db' => $di->lazyGet('database'),
);

// define the database service
$di->set('database', function() use ($di) {
    return $di->newInstance('example\package\Database');
});

// define the blog_model service
$di->set('blog_model', function() use ($di) {
    return $di->newInstance('example\package\BlogModel');
});

// define the wiki_model service
$di->set('wiki_model', function() use ($di) {
    return $di->newInstance('example\package\WikiModel');
});

我们不再需要在实例化的时候设置'db'的值。取而代之的是,子类BlogModelWikiModel从父类Model中继承该参数。因此,所有Model子类都能通过构造器参数db获得database服务。(当然,如果有需要可以在实例化的时候覆盖'db'的值。)

注意,这里我们使用了lazyGet()方法。这是一个特殊方法,一般与构造器参数和setter方法一起使用。如果我们调用$di->get()方法,容器将会在那会儿实例化服务。但是,如果使用$di->lazyGet()方法,当且仅当配置好的对象被成功实例化后,服务才会被实例化。想像它是某服务的延迟加载封装器,而该服务又是延迟加载的。

理所当然应该有一个lazyNew()方法,此方法是用于实例化对象,而不是获取服务。lazyNew()之于newInstance()就好比lazyGet()之于get()。使用lazyNew()方法,当配置好的类被创建后,才开始创建对象。

我们无需自己写类去获得配置系统的优点。任何具有构造器参数的类都会被配置系统识别,我们要做的仅仅是使用$di->newInstance()方法实例化类。


工厂和依赖实践

上面的应用只是为每个模型对象创建服务,这可能有点乏味。我们可能需要创建其他模型,并且我们不想分开创建他们。另外,我们可能要在另一个对象中创建模型对象,这也是工厂类的用武之地。

下面,我们将定义3个新类:为我们创建模型对象的工厂,使用模型工厂的抽像类PageController和需要博客模型的BlogController类。他们的定义如下:

<?php
namespace example\package;
use aura\di\Forge;

class ModelFactory
{
    protected $forge;

    public function __construct(Forge $forge)
    {
        $this->forge = $forge;
    }

    public function newInstance($model_name)
    {
        $class = 'example\package\Model' . ucfirst($model_name);
        return $this->forge->newInstance($class);
    }
}

abstract class PageController
{
    protected $model_factory;

    public function __construct(ModelFactory $model_factory)
    {
        $this->model_factory = $model_factory;
    }
}

class BlogController extends PageController
{
    public function exec()
    {
        $blog_model = $this->model_factory('blog');
        // ... get data from the blog model and return it ...
    }
}

现在,我们可以像这样建立DI容器:

<?php
// default params for database connections
$di->params['example\package\Database'] = array(
    'hostname' => 'localhost',
    'username' => 'user',
    'password' => 'passwd',
);

// default params for model objects
$di->params['example\package\Model'] = array(
    'db' => $di->lazyGet('database'),
);

// default params for the model factory
$di->params['example\package\ModelFactory'] = array(
    'forge' => $di->getForge(),
);

// default params for page controllers
$di->params['example\package\PageController'] = array(
    'model_factory' => $di->lazyGet('model_factory'),
);

// the database service
$di->set('database', function() use ($di) {
    return $di->newInstance('example\package\Database');
});

// the model factory service
$di->set('model_factory', function() use ($di) {
    return $di->newInstance('example\package\ModelFactory');
});

当我们实例化BlogController类并运行它时。。。

<?php
$blog_controller = $di->newInstance('aura\example\BlogController');
echo $blog_controller->exec();

。。。发生了一系列的事件,共分两步完成所有的依赖注入。第一步是实例化BlogController

第二步是在BlogController::exec()方法中调用ModelFactory::newInstance()

最后,BlogController::exec()方法获得了完全配置好的BlogModel对象,在该方法中不需要再做任何配置工作。


setter注入

上面我们一直在介绍构造器注入。然而,我们同样可以进行setter注入。

请看下面这个示例。。。

<?php
namespace example\package;

class Foo {

    protected $db;

    public function setDb(Database $db)
    {
        $this->db = $db;
    }
}

。。。一般来说,类属性的值应使用sertter方法设置,但是我们也可以用下面的注入方法设置他们:

<?php
// after construction, the Forge will call Foo::setDb()
// and inject the 'database' service object
$di->setter['example\package\Foo']['setDb'] = $di->lazyGet('database');

// create a foo_service; on get('foo_service'), the Forge will create the
// Foo object, then call setDb() on it per the setter specification above.
$di->set('foo_service', function() use ($di) {
    return $di->newInstance('example\package\Foo');
});

注意到我们在注入方法中使用了lazyGet()。和构造器参数一样,我们可以通过lazyNew()方法创建一个新的Database对象,而不是共享使用Manager中存在的那个:

<?php
// after construction, call Foo::setDb() and inject a service object.
// we override the default 'hostname' param for the instantiation.
$di->setter['example\package\Foo']['setDb'] = $di->lazyNew('example\package\Database', array(
    'hostname' => 'example.com',
));

// create a foo_service; on get('foo_service'), the Forge will create the
// Foo object, then call setDb() on it per the setter specification above.
$di->set('foo_service', function() use ($di) {
    return $di->newInstance('example\package\Foo');
});

setter的配置也是可继承的。如果你有一个example\package\Foo类,如。。。

<?php
namespace example\package;
class Bar extends Foo
{
    // ...
}

。。。你不需要为子类重新添加一个setter;Forge对象会查找它父类的所有setter,并应用它们。(如果你确要为子类添加一个setter,那会覆盖父类的setter。)


子容器

Manager本身是一个依赖注入容器,但是你可能希望创建Manager的子容器来处理服务。当你想要一个对象集合,对象之间互相引用,就好像是注册表或服务定位器的一部分,但是你又想保持依赖注入的优势,这就非常有用了。

添加子容器很简单,两行代码就能搞定:

<?php
// create a new Container
$sub = $di->newContainer('sub_name');

因为子容器是独立于Manager的,所以你也需要为它单独配置$params$setters

<?php
// get a previously-created Container
$sub = $di->getContainer('sub_name');

你可以使用以下方法操作子容器(其实和Container一样):

这些方法和Manager的一模一样,只是服务将会在子容器中创建而不是Manager

<?php
// create the sub-container
$sub = $di->newContainer('sub_name');

// configure params for objects in the sub-container
$sub->params['example\pacakage\Database']['host'] = 'alt-db.example.com';

// add an alternative Database connection to the sub-container
$sub->set('alt_db', function() use ($sub) {
    return $sub->newInstance('example\package\Database');
});

如果你想在子容器中添加更多的服务,或修改子容器的配置参数和setter,你可以先从Manager中取回子容器,再做相应的处理:

<?php
// get the sub-container
$sub = $di->get('sub_name');

// add another service to it
$sub->set('other', function() use ($sub) {
    return $sub->newInstance('example\package\Other');
});

最后,你还可以克隆子容器。这会创建子容器的一个拷贝,包括它的配置和服务定义,但会清除子容器中存储的服务对象。拷贝子容器中的服务对象将会在调用时被创建。这就允许你创建多个DI容器,就像独立注册表或服务定位器一样。

<?php
// clone 1 will have a set of service definitions and object instances...
// definitions ...
$clone1 = $di->cloneContainer('sub_name');

// ... and clone 2 will have the same service definitions, but the object
// instances will be different and independent from the ones in clone 1
$clone2 = $di->cloneContainer('sub_name');

如果你想把克隆子容器作为参数传递给另一个对象,可以使用lazyCloneContainer()方法。

<?php
$di->params['example\package\NeedsContainer']['container'] = $di->lazyCloneContainer('sub_name');

总结

如果我们使用参数、setter、服务和工厂正确地构建依赖注入,我们只需要直接从DI容器中获取一个对象。所有对象都会在DI容器中由工厂对象和/或Forge对象创建。我们永远都不会在任何创建的对象中直接使用DI容器本身。

返回顶部 ↑