weew/container

依赖注入容器。

v1.2.1 2016-07-21 11:17 UTC

README

Build Status Code Quality Test Coverage Version Licence

目录

安装

composer require weew/container

创建容器

容器没有其他依赖,因此简单实例化即可。

$container = new Container();

原始数据

存储任何类型的数据

$container->set('foo', 'bar');

// returns bar
$container->get('foo');

检索类

class Foo {}

// returns a new instance of Foo
$container->get(Foo::class);

传递附加参数

class Foo {
    public function __construct($x, $y = 2) {};
}

// parameters are matched by name
$container->get(Foo::class, ['x' => 1]);

解决构造函数依赖

class Foo {
    public function __construct(Bar $bar) {}
}
class Bar {}

// returns a new instance of Foo,
// Foo's constructor receives a new instance of Bar
$container->get(Foo::class);

共享特定实例

class Foo {}

$container->set(Foo::class, new Foo());
// or
$container->set(new Foo());

// everyone will get the same instance of Foo
$container->get(Foo::class);

工厂

使用工厂

class Foo {
    public $bar;
}
class Bar {}

// a factory method will get it's dependencies resolved too.
$container->set(Foo::class, function(Bar $bar) {
    $foo = new Foo();
    $foo->bar = $bar;

    return $foo;
});

在工厂内部访问容器

$container->set('foo', 1);

// a container can be injected the same way as any other dependency
$container->set('bar', function(IContainer $container) {
    return $container->get('foo');
));

容器不仅限于闭包工厂,也支持类方法和静态方法工厂。

class MyFactoryClass {
    public function factoryMethod(AnotherDependency $dependency) {}
    public function staticFactoryMethod(AnotherDependency $dependency) {}
}

$container->set(Foo::class, new MyFactoryClass(), 'factoryMethod');
$container->set(Foo::class, MyFactoryClass::class, 'factoryMethod');
$container->set(Foo::class, MyFactoryClass::class, 'staticFactoryMethod');

也支持传统的可调用数组语法。它与上面的例子完全相同,但语法略有不同。

$container->set(Foo::class, [new MyFactoryClass(), 'factoryMethod']);
$container->set(Foo::class, [MyFactoryClass::class, 'factoryMethod']);
$container->set(Foo::class, [MyFactoryClass::class, 'staticFactoryMethod']);

所有工厂都受益于依赖注入。另外,如果您让容器实例化您的工厂,它将通过容器进行解析。

接口

解析接口

interface IFoo {}
class Foo implements IFoo {}

$container->set(IFoo::class, Foo::class);

// will return an instance of Foo
$container->get(IFoo::class);

共享特定接口实现

interface IFoo {}
class Foo implements IFoo {}

$container->set(IFoo::class, new Foo());

// everyone will get the same instance of Foo
$container->get(IFoo::class);

接口也可以有工厂

interface IFoo {}
class Foo implements IFoo {}

$container->set(IFoo::class, function() {
    return new Foo();
});

// will return a new instance of Foo
$container->get(IFoo::class);

当然,您也可以为接口使用类型提示

interface IFoo {}
class Foo implements IFoo {}
class Bar {
    public function __construct(IFoo $foo) {}
}

$container->set(IFoo::class, Foo::class);

// returns an instance of Bar
// Bar receives an instance of Foo, which implements the interface IFoo
$container->get(Bar::class);

函数和方法

函数可以被容器解析

class Bar {}
function foo(Bar $bar, $foo) {}

// method foo gets called and receives an instance of Bar
// as with the other container methods, you can always pass your own arguments
$container->callFunction('foo', ['foo' => 1]);

这与闭包相同

class Bar {}

// closure gets called and receives an instance of Bar
$container->callFunction(function(Bar $bar) {});

调用类方法也很简单

class Foo {}
class Bar {
    public function takeFoo(Foo $foo, $x) {}
}

$bar = new Bar();
// method takeFoo gets invoked and receives a new instance
// of Foo, as well as the custom arguments
$container->callMethod($bar, 'takeFoo', ['x' => 1]);
// you could also let the container create an instance
$container->callMethod(Bar::class, 'takeFoo', ['x' => 1]);

调用静态方法

class Foo {}
class Bar {
    public static function takeFoo(Foo $foo, $x) {}
}

// method takeFoo gets invoked and receives a new instance
// of Foo, as well as the custom arguments
$container->callStaticMethod(Bar::class, 'takeFoo', ['x' => 1]);

可以使用 PHP 的传统可调用语法来调用函数和方法

// same as $container->callFunction($functionName, $args)
$container->call($functionName, $args);
// same as $container->callFunction($closure, $args)
$container->call($closure, $args);
// same as $container->callMethod($instance, $method, $args)
$container->call([$instance, $method], $args);
// same as $container->callMethod($className, $method, $args)
$container->call([$className, $method], $args);
// same as $container->callStaticMethod($className, $staticMethod, $args)
$container->call([$className, $staticMethod], $args);

单例

容器值可以定义为单例。单例定义将重复返回相同的值。以下是一个单例接口定义的示例

interface IFoo {}
class Foo implements IFoo {}

$container->set(IFoo::class, Foo::class)->singleton();

这同样适用于类

class Foo {}

$container->set(Foo::class)->singleton();

和工厂

class Foo {}

$container->set(Foo::class, function() {
    return new Foo();
})->singleton();

共享实例总是导致单例

class Foo {}

$container->set(Foo::class, new Foo())->singleton();
// same as
$container->set(Foo::class, new Foo());

通配符

这在使用工厂时可能特别有用。以 Doctrine 为例。您不能简单地自己实例化存储库。但仍然,如果容器可以解析它们,那就太好了。不幸的是,这将引发错误,因为存储库需要一个特殊参数,而这个参数可以也应该由容器解析

class MyRepository {
    public function __construct(SpecialUnresolvableValue $value) {}
}

$container->get(MyRepository::class);

但是,您可能可以使用通配符工厂。您可以使用任何正则表达式模式作为掩码。目前,唯一支持的正则表达式分隔符是 /#

class MyRepository implements IRepository {
    public function __construct(SpecialUnresolvableValue $value) {}
}
class YoursRepository implements IRepository {
    public function __construct(SpecialUnresolvableValue $value) {}
}

$container->set('/Repository$/', function(RepositoryFactory $factory, $abstract) {
    return $factory->createRepository($abstract);
});

$container->get(MyRepository::class);
$container->get(YourRepository::class);

正如您所看到的,实际的类名 MyRepository 被传递给自定义工厂作为 $abstract 参数。从那里,我们调用 RepositoryFactory 并告诉它为我们创建一个新的 MyRepository 实例。之后,同一个工厂可以用来创建 YourRepository 的实例。

告诉容器在这个工厂中产生的所有实例都应该作为单例是非常简单的

$container->set('/Repository$/', function(RepositoryFactory $factory, $abstract) {
    return $factory->createRepository($abstract);
})->singleton();

通配符非常强大,但是应该小心使用,因为配置不当可能会破坏您的应用程序。(例如:如果正则表达式掩码不够精确,并且与不想要的类匹配)。不过,由于正则表达式,创建精确的掩码不应该是个大问题。

通配符也可以与类名和实例结合使用。但我认为这种用法非常有限

$container->set('/Repository$/', EntityRepository::class);
$container->set('/Repository$/', $instance);

别名

如果您需要为定义创建别名,例如当您想为类及其接口提供工厂,而不想为每个都重复两次时,您可以通过别名创建定义(或两个、十个)。只需提供一个标识符数组。数组中的第一个元素被视为“id”,其余的是别名。

$container->set([MyImplementation::class, IImplementation::class], function() {
    return new MyImplementation('foo');
});

// both calls will return a value from the same factory
$container->get(MyImplementation::class);
$container->get(IImplementation::class);

同样的方法也适用于单例、原始值等。

附加方法

检查容器中是否有值

$container->set('foo', 'bar');

// will return true
$container->has('foo');

从容器中移除一个值

$container->set('foo', 'bar');
$container->remove('foo');

// will return false
$container->has('foo');

扩展

有额外的扩展可用,可以使容器更强大。

Doctrine 集成

weew/container-doctrine-integration 包使 doctrine 存储库可注入。