weew / container
Dependency injection container.
Requires
- weew/helpers-array: ^1.0.0
- weew/helpers-string: ^1.0.0
Requires (Dev)
- phpunit/phpunit: ^4.7
- satooshi/php-coveralls: ^0.6.1
README
Table of contents
- Installation
- Creating a container
- Primitive data
- Classes
- Factories
- Interfaces
- Functions and methods
- Singletons
- Wildcards
- Aliases
- Additional methods
- Extensions
Installation
composer require weew/container
Creating a container
The container has no additional dependencies, so a simple instantiation will do the trick.
$container = new Container();
Primitive data
Storing any type of data:
$container->set('foo', 'bar'); // returns bar $container->get('foo');
Classes
Retrieving classes:
class Foo {} // returns a new instance of Foo $container->get(Foo::class);
Passing additional parameters:
class Foo { public function __construct($x, $y = 2) {}; } // parameters are matched by name $container->get(Foo::class, ['x' => 1]);
Resolving constructor dependencies:
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);
Sharing a specific instance:
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);
Factories
Working with factories:
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; });
Accessing container from within a factory:
$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'); ));
Container is not limited to closure factories, it supports class method and static method factories too:
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');
Traditional callable array syntax is also supported. It does exactly the same as the examples above, but with a slightly different syntax:
$container->set(Foo::class, [new MyFactoryClass(), 'factoryMethod']); $container->set(Foo::class, [MyFactoryClass::class, 'factoryMethod']); $container->set(Foo::class, [MyFactoryClass::class, 'staticFactoryMethod']);
All facotires benefit from dependency injection. Additionaly, if you let the container instantiate your factory, it will be resolved trough the container too.
Interfaces
Resolving interfaces:
interface IFoo {} class Foo implements IFoo {} $container->set(IFoo::class, Foo::class); // will return an instance of Foo $container->get(IFoo::class);
Sharing specific interface implementation:
interface IFoo {} class Foo implements IFoo {} $container->set(IFoo::class, new Foo()); // everyone will get the same instance of Foo $container->get(IFoo::class);
Interfaces can have factories too:
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);
Of course you can also type hint interfaces:
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);
Functions and methods
Functions can get resolved by the container:
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]);
The same works for closures:
class Bar {} // closure gets called and receives an instance of Bar $container->callFunction(function(Bar $bar) {});
Invoking class methods is also strait forward:
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]);
Invoking static methods:
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]);
It is possible to use PHP's traditional callable syntax for invocation of functions and methods:
// 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);
Singletons
Container values can be defined as singletons. A singleton definition will return the same value over and over again. Here is an example of a singleton interface definition:
interface IFoo {} class Foo implements IFoo {} $container->set(IFoo::class, Foo::class)->singleton();
The same works for classes:
class Foo {} $container->set(Foo::class)->singleton();
And factories:
class Foo {} $container->set(Foo::class, function() { return new Foo(); })->singleton();
Sharing an instance always results in a singleton:
class Foo {} $container->set(Foo::class, new Foo())->singleton(); // same as $container->set(Foo::class, new Foo());
Wildcards
This one might be especially useful when working with factories. Lets take Doctrine for example. You can not simply instantiate a repository by yourself. But still, it would be great if you could have them resolved by the container. Unfortunately, this will throw an error, since the repository requires a special parameter that can and should not be resolved by the container:
class MyRepository { public function __construct(SpecialUnresolvableValue $value) {} } $container->get(MyRepository::class);
However, you might use a wildcard factory. You can use any regex pattern as a mask. Right now, the only supported regex delimiters are /
and #
.
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);
As you see here, the actual class name MyRepository
was passed to the custom factory as the $abstract
parameter. From there, we call the RepositoryFactory
and tell it to create us a new instance of MyRepository
. Afterwards the same factory can be used to create an instance of YourRepository
.
Telling the container that all instances produced within this factory should be singletons is very simple:
$container->set('/Repository$/', function(RepositoryFactory $factory, $abstract) { return $factory->createRepository($abstract); })->singleton();
Wildcards are very powerful, however, they should be used with caution, since they could break your application if you configure them wrong. (for example: if the regex mask is not precise enough and matches unwanted classes). Thanks to regex, creating precise masks shouldn't be a big deal though.
Wildcards can also be used in combination of class names and instances. But I find the usecases for this very limited:
$container->set('/Repository$/', EntityRepository::class); $container->set('/Repository$/', $instance);
Aliases
If you need to create an alias for a definition, for example when you want to provide a factory for a class as well as for it's interface, and don't want to do it twice for each one, you could create a definition with an alias (or two, or ten). Just provide an array of identifiers. The first element in the array is considered as "the id" and the others are aliases.
$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);
The same would work with singletons, primitive values and so on.
Additional methods
Check if the container has a value:
$container->set('foo', 'bar'); // will return true $container->has('foo');
Remove a value from the container:
$container->set('foo', 'bar'); $container->remove('foo'); // will return false $container->has('foo');
Extensions
There are additional extension available to make the container even more powerful.
Doctrine integration
The weew/container-doctrine-integration package makes doctrine repositories injectable.