Skip to content

Commit

Permalink
Merge pull request #45 from defunctl/master
Browse files Browse the repository at this point in the history
Allow Dependency Injection on Service Providers
  • Loading branch information
lucatume authored Jan 24, 2023
2 parents 7437ec0 + 5341b35 commit 51c11f1
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ to [Semantic Versioning](http://semver.org/).

## [unreleased] Unreleased

### Added
- The container now registers Service Providers using its own `Container::get()` method, instead of the `new` keyword. This allows Service Providers to utilize dependency injection. (thanks @defunctl).
- Additional contextual binding examples for primitives + service provider documentation in the README.

## [3.0.2] 2023-01-20;

### Added
Expand Down
92 changes: 92 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,14 @@ $container->bind(DbCache::class, function($container){
$container->when(TransactionManager::class)
->needs(CacheInterface::class)
->give(DbCache::class);

/*
* We can also bind primitives where the container doesn't know how to auto-wire
* them.
*/
$container->when(PaginationManager::class)
->needs('$per_page')
->give(25);
```

## Binding decorator chains
Expand Down Expand Up @@ -771,3 +779,87 @@ $legacyOne = $container->get(LegacyClassOne::class);
// Will not be called again here, done already.
$legacyTwo = $container->get(LegacyInterfaceTwo::class);
```

### Dependency injection with service providers

The container now supports additional dependency injection for service providers. Auto-wiring
will work the same as any class, simply override the service provider's constructor and add any additional concrete dependencies (don't forget to call the parent!):

```php
// file ProviderOne.php

use lucatume\DI52\ServiceProvider;

class ProviderOne extends ServiceProvider {

/**
* @var ConfigHelper
*/
protected $config;

public function __construct(\lucatume\DI52\Container $container, ConfigHelper $config)
{
parent::__construct($container);

$this->config = $config;
}

public function register()
{
$this->container->when(ClassFour::class)
->needs('$value')
->give($this->config->get('value'));
}

}

// Application bootstrap file.
use lucatume\DI52\Container;

$container = new Container();

$container->register(ProviderOne::class);
```
If you want to inject primitives into a service provider, you need to utilize the `when`, `needs`, `gives` methods **_before_** registering the provider in the container:

```php
// file ProviderOne.php

use lucatume\DI52\ServiceProvider;

class ProviderOne extends ServiceProvider {

/**
* @var bool
*/
protected $service_enabled;

public function __construct(\lucatume\DI52\Container $container, $service_enabled)
{
parent::__construct($container);

$this->service_enabled = $service_enabled;
}

public function register()
{
if (!$this->service_enabled) {
return;
}

$this->container->bind(InterfaceOne::class, ClassOne::class);
}

}

// Application bootstrap file.
use lucatume\DI52\Container;

$container = new Container();

$container->when(ProviderOne::class)
->needs('$service_enabled')
->give(true);

$container->register(ProviderOne::class);
```
3 changes: 2 additions & 1 deletion src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public function __construct($resolveUnboundAsSingletons = false)
{
$this->resolver = new Builders\Resolver($resolveUnboundAsSingletons);
$this->builders = new Builders\Factory($this, $this->resolver);
$this->singleton(Container::class, $this);
}

/**
Expand Down Expand Up @@ -450,7 +451,7 @@ protected function checkClassIsInstantiatable($class)
public function register($serviceProviderClass, ...$alias)
{
/** @var ServiceProvider $provider */
$provider = new $serviceProviderClass($this);
$provider = $this->get($serviceProviderClass);
if (!$provider->isDeferred()) {
$provider->register();
} else {
Expand Down
21 changes: 21 additions & 0 deletions tests/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,25 @@ public function should_throw_when_getting_provider_from_non_provider_binding()
$this->assertInstanceOf(NotFoundException::class, $e);
}
}

/**
* The container now registers itself inside itself (containerception).
*
* @test
*/
public function it_should_contain_the_containers_own_instance()
{
$container = new Container();

$this->assertTrue($container->has(Container::class));
$this->assertSame($container, $container->get(Container::class));

$container->get(ClassOne::class);
$container->get(ClassOneOne::class);

$this->assertTrue($container->has(ClassOne::class));
$this->assertTrue($container->has(ClassOneOne::class));
$this->assertSame($container, $container->get(Container::class));
}

}
104 changes: 104 additions & 0 deletions tests/ServiceProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,71 @@ public function register()
}
}

class TestDependency
{

public function getValue()
{
return 'test';
}
}

class TestProviderInjection extends ServiceProvider
{

/**
* @var TestDependency
*/
protected $dependency;

public function __construct(Container $container, TestDependency $dependency)
{
parent::__construct($container);

$this->dependency = $dependency;
}

public function register()
{
// TODO: Implement register() method.
}

public function getContainer() {
return $this->container;
}

public function getDependency()
{
return $this->dependency;
}
}

class TestProviderPrimitiveInjection extends ServiceProvider
{

/**
* @var string
*/
protected $value;

public function __construct(Container $container, $value)
{
parent::__construct($container);

$this->value = $value;
}

public function register()
{
// TODO: Implement register() method.
}

public function getValue()
{
return $this->value;
}
}

class ServiceProviderTest extends TestCase
{
/**
Expand Down Expand Up @@ -293,4 +358,43 @@ public function should_allow_registering_a_service_provider_with_multiple_aliase
$container->getProvider('other-service-provider')
);
}

/**
* Test we can create service providers with additional concrete dependencies.
*
* @test
*/
public function should_automatically_inject_concrete_instances_into_extended_providers()
{
$container = new Container();

$container->register(TestProviderInjection::class);
$this->assertTrue($container->has(TestProviderInjection::class));
$this->assertTrue($container->has(TestDependency::class));

$provider = $container->get(TestProviderInjection::class);

$this->assertSame($container, $provider->getContainer());
$this->assertInstanceOf(TestDependency::class, $provider->getDependency());
$this->assertSame('test', $provider->getDependency()->getValue());
}

/**
* Test we can configure the container with providers that have primitives
* in their constructor.
*
* @test
*/
public function should_allow_binding_providers_with_primitive_values()
{
$container = new Container();

$container->when(TestProviderPrimitiveInjection::class)
->needs('$value')
->give('test');

$container->register(TestProviderPrimitiveInjection::class);
$this->assertTrue($container->has(TestProviderPrimitiveInjection::class));
$this->assertSame('test', $container->get(TestProviderPrimitiveInjection::class)->getValue());
}
}

0 comments on commit 51c11f1

Please sign in to comment.