FactoryListContainer
Since v1.2016050901
Description
FactoryListContainer
is a container. It holds a list of factory methods and functions, indexed by name.
- factory name - the lookup key for the factory method or function
- factory - the factory method or function (can be any PHP
callable
, including__invoke
able objects)
FactoryListContainer
is a specialist dependency-injection container. It doesn't build dependencies for you. When you use it, it returns factory methods for you to call from your own code.
Public Interface
FactoryListContainer
has the following public interface:
// `FactoryListContainer` lives in this namespace
use GanbaroDigital\DIContainers\V1\FactoryList\Containers\FactoryListContainer;
// our base classes and interfaces
use ArrayAccess;
use GanbaroDigital\DIContainers\V1\Interfaces\FactoryList;
use GanbaroDigital\MissingBits\Entities\WriteProtectedEntity;
class FactoryListContainer
implements FactoryList, WriteProtectedEntity
{
/**
* create a managed list of factories
*
* the array has the following format:
* - 'key' is the name or alias for your factor
* - 'value' is a callable that will return something
*
* examples of keys include:
*
* - 'NotAFactoryList::newFromVar'
* - 'NotAFactory::newFromNonCallable'
*
* the only requirements on 'value' are that
* - it is a callable
*
* @param array $factories
* the list of factories to define
* @param FactoryList $exceptions
* the list of exceptions to throw
*/
public function __construct($factories, FactoryList $exceptions = null);
/**
* return the full list of aliases and builders as a real PHP array
*
* @return array
* @inheritedFrom FactoryList
*/
public function getList();
/**
* can we edit this entity?
*
* @return boolean
* FALSE if we can edit this container
* TRUE otherwise
* @inheritedFrom WriteProtectedEntity
*/
public function isReadOnly();
/**
* can we edit this container?
*
* @return boolean
* TRUE if we can edit this container
* FALSE otherwise
* @inheritedFrom WriteProtectedEntity
*/
public function isReadWrite();
/**
* disable editing this container
*
* @return void
* @inheritedFrom WriteProtectedEntity
*/
public function setReadOnly();
/**
* enable editing this container
*
* @return void
* @inheritedFrom WriteProtectedEntity
*/
public function setReadWrite();
}
How To Use
Defining Dependencies
FactoryListContainer
is a factory-driven DI container. You give it a list of factories (the factory) and their names:
use GanbaroDigital\DIContainers\V1\FactoryList\Containers\FactoryListContainer;
$deps = new FactoryListContainer([
'UnsupportedType::newFromVar' => [ UnsupportedType::class, 'newFromVar' ],
'NoSuchClass::newFromClassName' => [ NoSuchClass::class, 'newFromClassName' ],
]);
The array key is a string, and can be anything you like. We recommend that you use a classname::methodName pair. That makes it easier for other libraries (or apps) to replace your dependencies with their own should they need to.
FactoryListContainer
doesn't parse the array key. It's only used as a lookup key.
There are no plans to add any kind of magic in the future. We believe that magic makes it harder for someone else to reason about your code - especially if they've never used the same libraries and frameworks before.
The array value has to be a valid PHP callable
. This can be any one of:
- class name and static public method name pair
- object and non-static public method name pair
- global function name
- an object that has an
__invoke()
method - an anonymous function
Internally, we use PHP's is_callable()
function to check the array value. We throw an exception if is_callable()
rejects any of the array values.
The array value can accept any input parameters that it wants.
The array value - the factory - should create and return a new object every time it is called.
Creating Dependencies
Treat FactoryListContainer
as a PHP array that contains functions you can call:
use GanbaroDigital\DIContainers\V1\FactoryList\Containers\FactoryListContainer;
$deps = new FactoryListContainer([
'UnsupportedType::newFromVar' => [ UnsupportedType::class, 'newFromVar' ],
'NoSuchClass::newFromClassName' => [ NoSuchClass::class, 'newFromClassName' ],
]);
// use the DI container to create our exception
$exception = $deps['NoSuchClass::newFromClassName'](...);
In this example, we retrieve the factory from the container, and then call it ourselves. We are responsible for passing in any parameters that our chosen factory expects. As far as PHP is concerned, we're calling a PHP callable
directly from our code.
Note that FactoryListContainer
itself does not create your dependency. It returns the callable
factory for you to call.
Also be aware that FactoryListContainer
is not a service locator. Every time you use one of the factories in the FactoryListContainer
to create a dependency, you are creating a new instance of your dependency.
Adding Or Replacing Dependencies
FactoryListContainer
is an entity. It's read-only by default. You can put it into read-write mode and then treat it as a PHP array:
use GanbaroDigital\DIContainers\V1\FactoryList\Containers\FactoryListContainer;
$deps = new FactoryListContainer([
'UnsupportedType::newFromVar' => [ UnsupportedType::class, 'newFromVar' ],
]);
// replace the dependency
$deps->setReadWrite();
$deps['UnsupportedType::newFromVar'] = function() { throw new RuntimeException; };
In the code that ships to Production, treat FactoryListContainer
as a read-only container.
Only use this feature in your unit tests.
Removing Dependencies
FactoryListContainer
is an entity. It's read-only by default. You can put it into read-write mode and then treat it as a PHP array:
use GanbaroDigital\DIContainers\V1\FactoryList\Containers\FactoryListContainer;
$deps = new FactoryListContainer([
'UnsupportedType::newFromVar' => [ UnsupportedType::class, 'newFromVar' ],
]);
// remove the dependency
$deps->setReadWrite();
unset($deps['UnsupportedType::newFromVar']);
In the code that ships to Production, treat FactoryListContainer
as a read-only container.
Only use this feature in your unit tests.
Exceptions Thrown
When You Don't Provide A List Of Factories
If you do something like this:
use GanbaroDigital\DIContainers\V1\FactoryList\Containers\FactoryListContainer;
// cannot use NULL as a list of factories
$deps = new FactoryListContainer(null);
// cannot use booleans as a list of factories
$deps = new FactoryListContainer(true);
$deps = new FactoryListContainer(false);
// cannot use callables as a list of factories
$deps = new FactoryListContainer(function(){});
// cannot use doubles as a list of factories
$deps = new FactoryListContainer(3.1415927);
// cannot use integers as a list of factories
$deps = new FactoryListContainer(100);
// cannot use objects as a list of factories
$deps = new FactoryListContainer(new \stdClass);
// cannot use resources as a list of factories
$deps = new FactoryListContainer(STDIN);
// cannot use strings as a list of factories
$deps = new FactoryListContainer("BUILD ALL THE THINGS");
then FactoryListContainer
will throw a NotAListOfFactories
exception.
When You Provide Something That Isn't A Factory
If you do something like this:
use GanbaroDigital\DIContainers\V1\FactoryList\Containers\FactoryListContainer;
$deps = new FactoryListContainer([
// oops - a class name on its own isn't a PHP callable
'UnsupportedType::newFromVar' => UnsupportedType::class,
]);
then FactoryListContainer
will throw a NotAFactory
exception.
When The Factory Cannot Be Found
If you do something like this:
use GanbaroDigital\DIContainers\V1\FactoryList\Containers\FactoryListContainer;
$deps = new FactoryListContainer([
'UnsupportedType::newFromVar' => [ UnsupportedType::class, 'newFromVar' ],
]);
// oops ... 'NoSuchClass::newFromClassName' isn't registered!
$exception = $deps['NoSuchClass::newFromClassName'](...);
then FactoryListContainer
will throw a NoSuchFactory
exception.
Attempting To Edit A Read-Only Container
If you do something like this:
use GanbaroDigital\DIContainers\V1\FactoryList\Containers\FactoryListContainer;
$deps = new FactoryListContainer();
// oops - container is in read-only mode
$deps['UnsupportedType::newFromVar'] = [ UnsupportedType::class, 'newFromVar' ];
then FactoryListContainer
will throw a ContainerIsReadOnly
exception.
You can avoid this exception by making the container read-write first:
use GanbaroDigital\DIContainers\V1\FactoryList\Containers\FactoryListContainer;
$deps = new FactoryListContainer();
$deps->setReadWrite();
$deps['UnsupportedType::newFromVar'] = [ UnsupportedType::class, 'newFromVar' ];
Class Contract
Here is the contract for this class:
GanbaroDigital\DIContainers\V1\Exceptions\NoSuchFactory
[x] Can instantiate
[x] Is d i containers exception
[x] Is parameterised exception
[x] Is runtime exception
[x] Is http runtime error exception
[x] Maps to unexpected error status
[x] Can build from factory name
[x] Can pass caller filter into new from factory name
[x] New from factory name will use default caller filter if no filter provided
Class contracts are built from this class's unit tests.
Future releases of this class may add to this contract. New additions may include:
- clarifying existing behaviour (e.g. stricter contract around input or return types)
- add new behaviours (e.g. extra class methods)
When you use this class, you can only rely on the behaviours documented by this contract.
If you:
- find other ways to use this class,
- or depend on behaviours that are not covered by a unit test,
- or depend on undocumented internal states of this class,
... your code may not work in the future.
Notes
None at this time.
See Also
FactoryList
- interface for a dependency-injection container for factoriesNoSuchFactory
- exception thrown when we're asked for a factory, and have no factory registered under that nameNotAFactory
- exception thrown when we're given something that we cannot use as an factoryNotAListOfFactories
- exception thrown when we're given something we cannot use to build this containerWhy Another DI Solution?
- the story of how this container came to be