mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
NEW Add factory_method configuration to Injector
use callable as well as creator
This commit is contained in:
parent
3799bceff3
commit
1c85d151a6
@ -193,9 +193,14 @@ Note: undefined variables will be replaced with null.
|
||||
|
||||
## Factories
|
||||
|
||||
Some services require non-trivial construction which means they must be created by a factory class. To do this, create
|
||||
a factory class which implements the [Factory](api:SilverStripe\Framework\Injector\Factory) interface. You can then specify
|
||||
the `factory` key in the service definition, and the factory service will be used.
|
||||
Some services require non-trivial construction which means they must be created
|
||||
by a factory.
|
||||
|
||||
### Factory interface
|
||||
|
||||
Create a factory class which implements the [Factory](api:SilverStripe\Framework\Injector\Factory)
|
||||
interface. You can then specify the `factory` key in the service definition,
|
||||
and the factory service will be used.
|
||||
|
||||
An example using the `MyFactory` service to create instances of the `MyService` service is shown below:
|
||||
|
||||
@ -224,6 +229,32 @@ class MyFactory implements SilverStripe\Core\Injector\Factory
|
||||
$instance = Injector::inst()->get('MyService');
|
||||
```
|
||||
|
||||
### Factory method
|
||||
|
||||
To use any class that not implements Factory interface as a service factory
|
||||
specify `factory` and `factory_method` keys.
|
||||
|
||||
An example of HTTP Client service with extra logging middleware:
|
||||
|
||||
**app/_config/app.yml**
|
||||
|
||||
```yml
|
||||
SilverStripe\Core\Injector\Injector:
|
||||
LogMiddleware:
|
||||
factory: 'GuzzleHttp\Middleware'
|
||||
factory_method: 'log'
|
||||
constructor: ['%$Psr\Log\LoggerInterface', '%$GuzzleHttp\MessageFormatter', 'info']
|
||||
GuzzleHttp\HandlerStack:
|
||||
factory: 'GuzzleHttp\HandlerStack'
|
||||
factory_method: 'create'
|
||||
calls:
|
||||
- [push, ['%$LogMiddleware']]
|
||||
GuzzleHttp\Client:
|
||||
constructor:
|
||||
-
|
||||
handler: '%$GuzzleHttp\HandlerStack'
|
||||
```
|
||||
|
||||
## Dependency overrides
|
||||
|
||||
To override the `$dependency` declaration for a class, define the following configuration file.
|
||||
|
@ -605,8 +605,36 @@ class Injector implements ContainerInterface
|
||||
$constructorParams = [null, DataObject::CREATE_SINGLETON];
|
||||
}
|
||||
|
||||
$factory = isset($spec['factory']) ? $this->get($spec['factory']) : $this->getObjectCreator();
|
||||
$object = $factory->create($class, $constructorParams);
|
||||
if (isset($spec['factory']) && isset($spec['factory_method'])) {
|
||||
if (!method_exists($spec['factory'], $spec['factory_method'])) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Factory method "%s::%s" does not exist.',
|
||||
$spec['factory'],
|
||||
$spec['factory_method']
|
||||
));
|
||||
}
|
||||
|
||||
// If factory_method is statically callable, do not instantiate
|
||||
// factory i.e. just call factory_method statically.
|
||||
$factory = is_callable([$spec['factory'], $spec['factory_method']])
|
||||
? $spec['factory']
|
||||
: $this->get($spec['factory']);
|
||||
$method = $spec['factory_method'];
|
||||
$object = call_user_func_array([$factory, $method], $constructorParams);
|
||||
} else {
|
||||
$factory = isset($spec['factory']) ? $this->get($spec['factory']) : $this->getObjectCreator();
|
||||
if (!$factory instanceof Factory) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
'Factory class "%s" does not implement "%s" interface.',
|
||||
get_class($factory),
|
||||
Factory::class
|
||||
));
|
||||
}
|
||||
$object = $factory->create($class, $constructorParams);
|
||||
}
|
||||
if (!is_object($object)) {
|
||||
throw new InjectorNotFoundException('Factory does not return an object');
|
||||
}
|
||||
|
||||
// Handle empty factory responses
|
||||
if (!$object) {
|
||||
|
@ -118,6 +118,22 @@ class InjectorTest extends SapphireTest
|
||||
$injector->create('SomeClass');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fail creating object by factory that does not implement Factory
|
||||
* interface.
|
||||
*/
|
||||
public function testNotFactoryInterfaceFactory()
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$injector = new Injector([
|
||||
'service' => [
|
||||
'factory' => 'stdClass',
|
||||
],
|
||||
]);
|
||||
$injector->get('service');
|
||||
}
|
||||
|
||||
public function testConfiguredInjector()
|
||||
{
|
||||
$injector = new Injector();
|
||||
@ -906,6 +922,80 @@ class InjectorTest extends SapphireTest
|
||||
$this->assertInstanceOf(TestObject::class, $injector->get('service'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating object by factory method.
|
||||
*/
|
||||
public function testByFactoryMethodObjectCreator()
|
||||
{
|
||||
// Dummy service giving DateTime of tommorow.
|
||||
$injector = new Injector([
|
||||
'service' => [
|
||||
'factory' => 'DateTime',
|
||||
'factory_method' => 'add',
|
||||
'constructor' => ['%$DateInterval'],
|
||||
],
|
||||
'DateInterval' => [
|
||||
'constructor' => ['P1D'],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertInstanceOf(\DateTime::class, $injector->get('service'));
|
||||
$this->assertEquals(
|
||||
(new \DateTime())->add(new \DateInterval('P1D'))->format('%Y%m%d'),
|
||||
$injector->get('service')->format('%Y%m%d')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creating object by static factory method.
|
||||
*/
|
||||
public function testByStaticFactoryMethodObjectCreator()
|
||||
{
|
||||
// Dummy service changing any callable to injector service with
|
||||
// `strtoupper` as default one. Constructor disallows instantiation.
|
||||
$injector = new Injector([
|
||||
'service' => [
|
||||
'factory' => 'Closure',
|
||||
'factory_method' => 'fromCallable',
|
||||
'constructor' => ['strtoupper'],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertInstanceOf(\Closure::class, $injector->get('service'));
|
||||
|
||||
// Default service.
|
||||
$this->assertEquals('ABC', $injector->get('service')('abc'));
|
||||
|
||||
// Create service with arguments.
|
||||
$this->assertEquals('abc', $injector->create('service', 'strtolower')('ABC'));
|
||||
}
|
||||
|
||||
public function testFactoryMethodNotReturnsObject()
|
||||
{
|
||||
$this->expectException(InjectorNotFoundException::class);
|
||||
|
||||
$injector = new Injector([
|
||||
'service' => [
|
||||
'factory' => 'DateTime',
|
||||
'factory_method' => 'getTimeStamp',
|
||||
],
|
||||
]);
|
||||
$injector->get('service');
|
||||
}
|
||||
|
||||
public function testFactoryMethodNotExists()
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
|
||||
$injector = new Injector([
|
||||
'service' => [
|
||||
'factory' => 'stdClass',
|
||||
'factory_method' => 'method',
|
||||
],
|
||||
]);
|
||||
$injector->get('service');
|
||||
}
|
||||
|
||||
public function testMethods()
|
||||
{
|
||||
// do it again but have test object configured as a constructor dependency
|
||||
|
Loading…
Reference in New Issue
Block a user