Skip to content

Commit

Permalink
- add PSR-11 support
Browse files Browse the repository at this point in the history
- closes #8
  • Loading branch information
Andy Vaughn committed Dec 12, 2020
1 parent 09826d0 commit 8bd3bb4
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 42 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
},
"require": {
"php": "~7.4.12",
"psr/container": "~1.0.0",
"ocramius/proxy-manager": "~2.10.0"
},
"require-dev": {
Expand Down
6 changes: 4 additions & 2 deletions src/Exceptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
namespace modethirteen\OpenContainer;

use Exception;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;

class OpenContainerCannotBuildDeferredInstanceException extends Exception {
class OpenContainerCannotBuildDeferredInstanceException extends Exception implements ContainerExceptionInterface {

/**
* @param string $id
Expand All @@ -32,7 +34,7 @@ public function __construct(string $id) {
}
}

class OpenContainerNotRegisteredInContainerException extends Exception {
class OpenContainerNotRegisteredInContainerException extends Exception implements NotFoundExceptionInterface {

/**
* @param string $id
Expand Down
3 changes: 2 additions & 1 deletion src/IContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@
namespace modethirteen\OpenContainer;

use Closure;
use Psr\Container\ContainerInterface;

/**
* Interface IContainer
* @package modethirteen\OpenContainer
*/
interface IContainer {
interface IContainer extends ContainerInterface {

/**
* @param string $id
Expand Down
32 changes: 27 additions & 5 deletions src/OpenContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ public function __construct() {
* @throws OpenContainerCannotBuildDeferredInstanceException
*/
public function __get(string $id) : object {
return $this->get($id);
}

public function flushInstance(string $id) : void {
if(isset($this->instances[$id])) {
unset($this->instances[$id]);
}
}

/**
* @note type hint is not leveraged in this method as the psr/container interface does not include it
* @param string $id
* @return mixed|void
* @noinspection PhpMissingParamTypeInspection
* @throws OpenContainerNotRegisteredInContainerException
* @throws OpenContainerCannotBuildDeferredInstanceException
*/
public function get($id) {
if($this->isDeferred && isset($this->deferredInstances[$id])) {

// requesting a previously generated deferred instance
Expand Down Expand Up @@ -107,7 +125,7 @@ public function __get(string $id) : object {
// build deferred instance
$instance = $this->deferredInstanceFactory
->createProxy($type, function(&$instance, LazyLoadingInterface $proxy) use ($builder, $type) : bool {
$proxy->setProxyInitializer(null);
$proxy->setProxyInitializer();
$instance = $builder !== null ? $builder($this) : new $type($this);
return true;
});
Expand All @@ -121,10 +139,14 @@ public function __get(string $id) : object {
return $instance;
}

public function flushInstance(string $id) : void {
if(isset($this->instances[$id])) {
unset($this->instances[$id]);
}
/**
* @note type hint is not leveraged in this method as the psr/container interface does not include it
* @param string $id
* @return bool
* @noinspection PhpMissingParamTypeInspection
*/
public function has($id) : bool {
return $this->isRegistered($id);
}

public function isDeferredContainer(): bool {
Expand Down
2 changes: 2 additions & 0 deletions tests/DependencyContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,7 @@ public function __construct() {
return [];
});
$this->registerInstance('Instance', new Instance());
$this->registerType('PsrCompatibleCircularDependencyOne', PsrCompatibleCircularDependencyOne::class);
$this->registerType('PsrCompatibleCircularDependencyTwo', PsrCompatibleCircularDependencyTwo::class);
}
}
117 changes: 83 additions & 34 deletions tests/OpenContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,52 +29,77 @@ class OpenContainerTest extends TestCase {
/**
* @return array
*/
public static function containerProvider() : array {
public static function container_Provider() : array {
return [
'container' => [new DependencyContainer()],
'deferred container' => [(new DependencyContainer())->toDeferredContainer()]
'deferred container' => [(new DependencyContainer())->toDeferredContainer()],
];
}

/**
* @dataProvider containerProvider
* @return array
*/
public static function container_psr_Provider() : array {
return [
'container' => [new DependencyContainer(), false],
'deferred container' => [(new DependencyContainer())->toDeferredContainer(), false],
'container with psr interface' => [new DependencyContainer(), true],
'deferred container with psr interface' => [(new DependencyContainer())->toDeferredContainer(), true]
];
}

/**
* @return array
*/
public static function psr_Provider() : array {
return [
'with psr interface' => [true],
'without psr interface' => [false]
];
}

/**
* @dataProvider container_psr_Provider
* @param IDependencyContainer $container
* @param bool $psr
* @test
*/
public function Can_handle_instance_registration(IDependencyContainer $container) : void {
public function Can_handle_instance_registration(IDependencyContainer $container, bool $psr) : void {

// act
$result = $container->Instance;
$result = $psr ? $container->get('Instance') : $container->Instance;

// assert
static::assertInstanceOf(Instance::class, $result);
}

/**
* @dataProvider containerProvider
* @dataProvider container_psr_Provider
* @param IDependencyContainer $container
* @param bool $psr
* @test
*/
public function Can_handle_type_registration(IDependencyContainer $container) : void {
public function Can_handle_type_registration(IDependencyContainer $container, bool $psr) : void {

// arrange
$container->flushInstance('Instance');
$container->registerType('Plugh', Instance::class);

// act
/** @noinspection PhpUndefinedFieldInspection */
$result = $container->Plugh;
$result = $psr ? $container->get('Plugh') : $container->Plugh;

// assert
static::assertInstanceOf(Instance::class, $result);
}

/**
* @dataProvider containerProvider
* @dataProvider container_psr_Provider
* @param IDependencyContainer $container
* @param bool $psr
* @test
*/
public function Can_handle_builder_registration(IDependencyContainer $container) : void {
public function Can_handle_builder_registration(IDependencyContainer $container, bool $psr) : void {

// arrange
$container->flushInstance('Instance');
Expand All @@ -85,60 +110,83 @@ public function Can_handle_builder_registration(IDependencyContainer $container)

// act
/** @noinspection PhpUndefinedFieldInspection */
$result = $container->Xyzzy;
$result = $psr ? $container->get('Xyzzy') : $container->Xyzzy;

// assert
static::assertInstanceOf(Instance::class, $result);
}

/**
* @dataProvider containerProvider
* @dataProvider container_psr_Provider
* @param IDependencyContainer $container
* @param bool $psr
* @test
*/
public function Can_handle_circular_dependency_resolution(IDependencyContainer $container) : void {
public function Can_handle_circular_dependency_resolution(IDependencyContainer $container, bool $psr) : void {

// arrange
if($psr && !$container->isDeferredContainer()) {
static::markTestSkipped('Using the PSR-11 container "get" method with a non-deferred container creates an endless nested function loop');
}

// act
$foo = $container->CircularDependencyOne;
$bar = $container->CircularDependencyTwo;
$foo = $psr ? $container->get('PsrCompatibleCircularDependencyOne') : $container->CircularDependencyOne;
$bar = $psr ? $container->get('PsrCompatibleCircularDependencyTwo') : $container->CircularDependencyTwo;

// assert
if($container->isDeferredContainer()) {

// deferred container can manage circular dependencies at construction
static::assertInstanceOf(CircularDependencyOne::class, $foo);
static::assertInstanceOf(CircularDependencyTwo::class, $bar);
static::assertInstanceOf(CircularDependencyOne::class, $bar->getDependency());
static::assertInstanceOf(CircularDependencyTwo::class, $foo->getDependency());
if($psr) {
static::assertInstanceOf(PsrCompatibleCircularDependencyOne::class, $foo);
static::assertInstanceOf(PsrCompatibleCircularDependencyTwo::class, $bar);
static::assertInstanceOf(PsrCompatibleCircularDependencyOne::class, $bar->getDependency());
static::assertInstanceOf(PsrCompatibleCircularDependencyTwo::class, $foo->getDependency());
} else {
static::assertInstanceOf(CircularDependencyOne::class, $foo);
static::assertInstanceOf(CircularDependencyTwo::class, $bar);
static::assertInstanceOf(CircularDependencyOne::class, $bar->getDependency());
static::assertInstanceOf(CircularDependencyTwo::class, $foo->getDependency());
}
} else {

// without deferred container, circular dependencies are presented as null in injectable class constructors
static::assertInstanceOf(CircularDependencyOne::class, $foo);
static::assertInstanceOf(CircularDependencyTwo::class, $bar);
static::assertNull($bar->getDependency());
static::assertInstanceOf(CircularDependencyTwo::class, $foo->getDependency());
if($psr) {
static::assertInstanceOf(PsrCompatibleCircularDependencyOne::class, $foo);
static::assertInstanceOf(PsrCompatibleCircularDependencyTwo::class, $bar);
static::assertNull($bar->getDependency());
static::assertInstanceOf(PsrCompatibleCircularDependencyTwo::class, $foo->getDependency());
} else {
static::assertInstanceOf(CircularDependencyOne::class, $foo);
static::assertInstanceOf(CircularDependencyTwo::class, $bar);
static::assertNull($bar->getDependency());
static::assertInstanceOf(CircularDependencyTwo::class, $foo->getDependency());
}
}
}

/**
* @dataProvider containerProvider
* @dataProvider container_psr_Provider
* @param IDependencyContainer $container
* @param bool $psr
* @test
*/
public function Can_handle_unregistered_dependency(IDependencyContainer $container) {
public function Can_handle_unregistered_dependency(IDependencyContainer $container, bool $psr) : void {

// assert
static::expectException(OpenContainerNotRegisteredInContainerException::class);

// act
/** @noinspection PhpUndefinedFieldInspection */
$container->Ogre;
$psr ? $container->get('Ogre') : $container->Ogre;
}

/**
* @dataProvider psr_Provider
* @param bool $psr
* @test
*/
public function Can_handle_deferred_dependency_construction_error() {
public function Can_handle_deferred_dependency_construction_error(bool $psr) : void {

// assert
static::expectException(OpenContainerCannotBuildDeferredInstanceException::class);
Expand All @@ -150,31 +198,32 @@ public function Can_handle_deferred_dependency_construction_error() {
});

// act
$container->Puppy;
$psr ? $container->get('Puppy') : $container->Puppy;
}

/**
* @dataProvider containerProvider
* @dataProvider container_psr_Provider
* @param IDependencyContainer $container
* @param bool $psr
* @test
*/
public function Can_check_if_dependency_is_registered(IDependencyContainer $container) {
public function Can_check_if_dependency_is_registered(IDependencyContainer $container, bool $psr) : void {

// act
$result1 = $container->isRegistered('Instance');
$result2 = $container->isRegistered('Fred');
$result1 = $psr ? $container->has('Instance') : $container->isRegistered('Instance');
$result2 = $psr ? $container->has('Fred') : $container->isRegistered('Fred');

// assert
static::assertTrue($result1);
static::assertFalse($result2);
}

/**
* @dataProvider containerProvider
* @dataProvider container_Provider
* @param IDependencyContainer $container
* @test
*/
public function Can_check_if_dependency_is_resolved(IDependencyContainer $container) {
public function Can_check_if_dependency_is_resolved(IDependencyContainer $container) : void {

// arrange
$container->registerType('Plugh', Instance::class);
Expand Down
43 changes: 43 additions & 0 deletions tests/PsrCompatibleCircularDependencyOne.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php declare(strict_types=1);
/**
* OpenContainer - a dependency injection container for PHP
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace modethirteen\OpenContainer\Tests;

/**
* Class PsrCompatibleCircularDependencyOne
* @package modethirteen\OpenContainer\Tests
*/
class PsrCompatibleCircularDependencyOne {

/**
* @var PsrCompatibleCircularDependencyTwo|null
*/
private ?PsrCompatibleCircularDependencyTwo $dependency;

/**
* @param IDependencyContainer $container
*/
public function __construct(IDependencyContainer $container) {
$this->dependency = $container->get('PsrCompatibleCircularDependencyTwo');
}

/**
* @return PsrCompatibleCircularDependencyTwo|null
*/
public function getDependency() : ?PsrCompatibleCircularDependencyTwo {
return $this->dependency;
}
}
Loading

0 comments on commit 8bd3bb4

Please sign in to comment.