Refactor container to be more flexible

This commit is contained in:
Timothy Warren 2016-08-29 12:51:40 -04:00
parent 3c782af86b
commit 3db58c7066
5 changed files with 245 additions and 56 deletions

View File

@ -28,11 +28,18 @@ use Aviat\Ion\Di\Exception\NotFoundException;
class Container implements ContainerInterface {
/**
* Array with class instances
* Array of container Generator functions
*
* @var Callable[]
*/
protected $container = [];
/**
* Array of object instances
*
* @var array
*/
protected $container = [];
protected $instances = [];
/**
* Map of logger instances
@ -55,10 +62,10 @@ class Container implements ContainerInterface {
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id Identifier of the entry to look for.
* @param string $id - Identifier of the entry to look for.
*
* @throws NotFoundException No entry was found for this identifier.
* @throws ContainerException Error while retrieving the entry.
* @throws NotFoundException - No entry was found for this identifier.
* @throws ContainerException - Error while retrieving the entry.
*
* @return mixed Entry.
*/
@ -71,40 +78,88 @@ class Container implements ContainerInterface {
if ($this->has($id))
{
$item = $this->container[$id];
// Return an object instance, if it already exists
if (array_key_exists($id, $this->instances))
{
return $this->instances[$id];
}
if (is_callable($item))
{
return $this->applyContainer($item($this));
}
else
{
return $item;
}
// If there isn't already an instance, create one
$obj = $this->getNew($id);
$this->instances[$id] = $obj;
return $obj;
}
throw new NotFoundException("Item '{$id}' does not exist in container.");
}
/**
* Add a value to the container
* Get a new instance of the specified item
*
* @param string $id - Identifier of the entry to look for.
* @param array [$args] - Optional arguments for the factory callable
* @throws NotFoundException - No entry was found for this identifier.
* @throws ContainerException - Error while retrieving the entry.
* @return mixed
*/
public function getNew($id, array $args = NULL)
{
if ( ! is_string($id))
{
throw new ContainerException("Id must be a string");
}
if ($this->has($id))
{
// By default, call a factory with the Container
$args = (is_array($args)) ? $args : [$this];
$obj = call_user_func_array($this->container[$id], $args);
// Check for container interface, and apply the container to the object
// if applicable
return $this->applyContainer($obj);
}
throw new NotFoundException("Item '{$id}' does not exist in container.");
}
/**
* Add a factory to the container
*
* @param string $id
* @param mixed $value
* @param Callable $value - a factory callable for the item
* @return ContainerInterface
*/
public function set($id, $value)
public function set($id, Callable $value)
{
$this->container[$id] = $value;
return $this;
}
/**
* Set a specific instance in the container for an existing factory
*
* @param string $id
* @param mixed $value
* @throws NotFoundException - No entry was found for this identifier.
* @return ContainerInterface
*/
public function setInstance($id, $value)
{
if ( ! $this->has($id))
{
throw new NotFoundException("Factory '{$id}' does not exist in container. Set that first.");
}
$this->instances[$id] = $value;
return $this;
}
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* @param string $id Identifier of the entry to look for.
*
* @return boolean
*/
public function has($id)
@ -114,37 +169,38 @@ class Container implements ContainerInterface {
/**
* Determine whether a logger channel is registered
* @param string $key The logger channel
*
* @param string $id The logger channel
* @return boolean
*/
public function hasLogger($key = 'default')
public function hasLogger($id = 'default')
{
return array_key_exists($key, $this->loggers);
return array_key_exists($id, $this->loggers);
}
/**
* Add a logger to the Container
*
* @param LoggerInterface $logger
* @param string $key The logger 'channel'
* @param string $id The logger 'channel'
* @return ContainerInterface
*/
public function setLogger(LoggerInterface $logger, $key = 'default')
public function setLogger(LoggerInterface $logger, $id = 'default')
{
$this->loggers[$key] = $logger;
$this->loggers[$id] = $logger;
return $this;
}
/**
* Retrieve a logger for the selected channel
*
* @param string $key The logger to retrieve
* @param string $id The logger to retrieve
* @return LoggerInterface|null
*/
public function getLogger($key = 'default')
public function getLogger($id = 'default')
{
return ($this->hasLogger($key))
? $this->loggers[$key]
return ($this->hasLogger($id))
? $this->loggers[$id]
: NULL;
}

View File

@ -16,43 +16,61 @@
namespace Aviat\Ion\Di;
use Interop\Container\ContainerInterface as InteropInterface;
use Psr\Log\LoggerInterface;
/**
* Interface for the Dependency Injection Container
*/
interface ContainerInterface extends \Interop\Container\ContainerInterface {
interface ContainerInterface extends InteropInterface {
/**
* Add a value to the container
* Add a factory to the container
*
* @param string $key
* @param mixed $value
* @param string $id
* @param Callable $value - a factory callable for the item
* @return ContainerInterface
*/
public function set($key, $value);
public function set($id, Callable $value);
/**
* Set a specific instance in the container for an existing factory
*
* @param string $id
* @param mixed $value
* @return ContainerInterface
*/
public function setInstance($id, $value);
/**
* Get a new instance of the specified item
*
* @param string $id
* @return mixed
*/
public function getNew($id);
/**
* Determine whether a logger channel is registered
* @param string $key The logger channel
* @param string $id The logger channel
* @return boolean
*/
public function hasLogger($key = 'default');
public function hasLogger($id = 'default');
/**
* Add a logger to the Container
*
* @param LoggerInterface $logger
* @param string $key The logger 'channel'
* @param string $id The logger 'channel'
* @return Container
*/
public function setLogger(LoggerInterface $logger, $key = 'default');
public function setLogger(LoggerInterface $logger, $id = 'default');
/**
* Retrieve a logger for the selected channel
*
* @param string $key The logger to retreive
* @param string $id The logger to retreive
* @return LoggerInterface|null
*/
public function getLogger($key = 'default');
public function getLogger($id = 'default');
}

View File

@ -8,9 +8,22 @@ use Monolog\Logger;
use Monolog\Handler\TestHandler;
use Monolog\Handler\NullHandler;
class FooTest {
public $item;
public function __construct($item) {
$this->item = $item;
}
}
class FooTest2 {
use \Aviat\Ion\Di\ContainerAware;
}
class ContainerTest extends \Ion_TestCase {
public function setUp()
{
$this->container = new Container();
@ -53,6 +66,94 @@ class ContainerTest extends \Ion_TestCase {
}
}
/**
* @dataProvider dataGetWithException
*/
public function testGetNewWithException($id, $exception, $message)
{
try
{
$this->container->getNew($id);
}
catch(ContainerException $e)
{
$this->assertInstanceOf($exception, $e);
$this->assertEquals($message, $e->getMessage());
}
}
public function dataSetInstanceWithException()
{
return [
'Non-existent id' => [
'id' => 'foo',
'exception' => 'Aviat\Ion\Di\Exception\NotFoundException',
'message' => "Factory 'foo' does not exist in container. Set that first.",
],
'Non-existent id 2' => [
'id' => 'foobarbaz',
'exception' => 'Aviat\Ion\Di\Exception\NotFoundException',
'message' => "Factory 'foobarbaz' does not exist in container. Set that first.",
],
];
}
/**
* @dataProvider dataSetInstanceWithException
*/
public function testSetInstanceWithException($id, $exception, $message)
{
try
{
$this->container->setInstance($id, NULL);
}
catch(ContainerException $e)
{
$this->assertInstanceOf($exception, $e);
$this->assertEquals($message, $e->getMessage());
}
}
public function testGetNew()
{
$this->container->set('footest', function($item) {
return new FooTest($item);
});
// Check that the item is the container, if called without arguments
$footest1 = $this->container->getNew('footest');
$this->assertInstanceOf('Aviat\Ion\Di\ContainerInterface', $footest1->item);
$footest2 = $this->container->getNew('footest', ['Test String']);
$this->assertEquals('Test String', $footest2->item);
}
public function testSetContainerInInstance()
{
$this->container->set('footest2', function() {
return new FooTest2();
});
$footest2 = $this->container->get('footest2');
$this->assertEquals($this->container, $footest2->getContainer());
}
public function testGetNewReturnCallable()
{
$this->container->set('footest', function($item) {
return function() use ($item) {
return $item;
};
});
// Check that the item is the container, if called without arguments
$footest1 = $this->container->getNew('footest');
$this->assertInstanceOf('Aviat\Ion\Di\ContainerInterface', $footest1());
$footest2 = $this->container->getNew('footest', ['Test String']);
$this->assertEquals('Test String', $footest2());
}
public function testGetSet()
{
$container = $this->container->set('foo', function() {
@ -61,6 +162,8 @@ class ContainerTest extends \Ion_TestCase {
$this->assertInstanceOf('Aviat\Ion\Di\Container', $container);
$this->assertInstanceOf('Aviat\Ion\Di\ContainerInterface', $container);
// The factory returns a callable
$this->assertTrue(is_callable($container->get('foo')));
}

View File

@ -29,13 +29,13 @@ class Ion_TestCase extends PHPUnit_Framework_TestCase {
protected static $staticContainer;
protected static $session_handler;
public static function setUpBeforeClass()
/*public static function setUpBeforeClass()
{
// Use mock session handler
$session_handler = new TestSessionHandler();
session_set_save_handler($session_handler, TRUE);
self::$session_handler = $session_handler;
}
}*/
public function setUp()
{
@ -86,7 +86,12 @@ class Ion_TestCase extends PHPUnit_Framework_TestCase {
// Set up DI container
$di = require('di.php');
$container = $di($config_array);
$container->set('session-handler', self::$session_handler);
$container->set('session-handler', function() {
// Use mock session handler
$session_handler = new TestSessionHandler();
session_set_save_handler($session_handler, TRUE);
return $session_handler;
});
$this->container = $container;
}
@ -111,7 +116,7 @@ class Ion_TestCase extends PHPUnit_Framework_TestCase {
['Zend\Diactoros\ServerRequestFactory', 'fromGlobals'],
array_merge($default, $supers)
);
$this->container->set('request', $request);
$this->container->setInstance('request', $request);
}
}
// End of Ion_TestCase.php

View File

@ -12,20 +12,27 @@ use Aviat\Ion\Di\Container;
// Setup DI container
// -----------------------------------------------------------------------------
return function(array $config_array = []) {
$container = new Container([
'config' => new Config($config_array),
]);
$container = new Container();
// Create Request/Response Objects
$request = ServerRequestFactory::fromGlobals(
$_SERVER,
$_GET,
$_POST,
$_COOKIE,
$_FILES
);
$container->set('request', $request);
$container->set('response', new Response());
$container->set('config', function() {
return new Config([]);
});
$container->setInstance('config', new Config($config_array));
$container->set('request', function() {
return ServerRequestFactory::fromGlobals(
$_SERVER,
$_GET,
$_POST,
$_COOKIE,
$_FILES
);
});
$container->set('response', function() {
return new Response();
});
// Create session Object
$container->set('session', function() {