-
Notifications
You must be signed in to change notification settings - Fork 2
Services
Services in SpiffyFramework are managed by the SpiffyInject component. SpiffyInject is SpiffyFramework's light-weight inversion of control container and is part of the foundation to the entire framework.
SpiffyInject also manage's package parameters. You can read more about this feature in the package documentation.
You can create services in the following ways:
- Setting using a string class name
- Setting the service directly
- Creating the service through a closure
- Using the array configuration
- Using an object that implements ServiceFactory
- Using annotations
All services are set through the nject()
method regardless of which style you choose. Each style has it's own advantages and disadvantages. It's you to you to decide which is the best approach to take for your application.
By default, each package loads services using the services.php file in config/services.php
. Below is a simple example of the config/services.php
file.
// file: application/config/services.php
// 'service_name' => 'service_specification'
return [
'application.service' => 'Application\Service',
];
// file: application/config/services.php
return [
'application.service' => 'Application\Service',
];
Referencing application.service
would return an object that's an instance of Application\Service
.
// file: application/config/services.php
return [
'application.service' => new \Application\Service(),
];
Referencing application.service
would return an object that's an instance of Application\Service
but would create the service on every request. In general this method is not recommended because it's impossible to lazy-load the service.
// file: application/config/services.php
return [
'application.service' => function(\Spiffy\Inject\Injector $i) {
new \Application\Service($i->nvoke('foo'));
},
];
Using a closure allows you to inject additional dependencies before returning the service. A closure is good for rapid prototyping but is not recommended for production because closures can not be serialized. This means that you can not cache the package manager configuration.
// file:: application/src/MyServiceFactory;
namespace Application;
use Spiffy\Inject\Injector;
use Spiffy\Inject\ServiceFactory;
class MyServiceFactory implements ServiceFactory
{
public function createService(Injector $i)
{
return new Service($i->nvoke('foo'));
}
}
// file: application/config/services.php
return [
'application.service' => 'Application\MyServiceFactory'
];
Using a service factory is a preferred method for creating services. By using a factory you can inject additional dependencies, cache the package manager configuration, and easily unit test your factory. A factory also provides you with the most flexibility for complex services.
// file: application/config/services.php
return [
'foo' => '\StdClass',
'application.service' => ['Application\Service', ['@foo']]
];
This is identical to the service factory above. An instance of Application\Service
is created and the 'foo' service is injected into the constructor. The array configuration is designed to provide you a rapid way to basic services with basic dependencies. This method is also able to be cached. This is a preferred method for creating services.
Read on for more information on the array configuration.
The array configuration allows constructor injection through the second array parameter passed to the service.
// file: application/config/services.php
return [
'foo' => '\StdClass',
'application.service' => ['Application\Service', ['@foo']]
];
The special '@' notation references another service for injection. In the example above Application\Service
would be constructor injected with the @foo
service which is an instance of StdClass
. Constructor injection should be used for immutable dependencies.
// file: application/config/services.php
return [
'foo' => '\StdClass',
'application.service' => ['Application\Service', [], ['setFoo' => '@foo']]
];
SpiffyInject allows for setter injection in addition to constructor injection through the third array parameter. The key should be the name of the method and the value should be the value to inject. The example above creates the Application\Service
and then injects the @foo
service using the setFoo()
method. Setter injection should be used for mutable dependencies.
SpiffyInject's array configuration includes the ability to reference other services when the string begins with special identifier characters. By default you reference services with the @
symbol and parameters with the $
symbol. These can be modified using the setServiceIdentifier
and setParamIdentifier
methods respectively on the Injector.
Each package's configuration is automatically created and available for use when defining your services. Read more about this in the package documentation.
Often times you may want to override the default behavior of a service. This is particularly useful when using third-party packages. SpiffyInject has two methods available for overriding services.
Decorating services allows you to take the service created and apply any modifications to it prior to having it returned. Decorators are defined by returning an additional array from the config/services.php
file.
// file: application/config/services.php
return [
'foo' => '\StdClass',
'application.service' => ['Application\Service', [], ['setFoo' => '@foo']]
], [
'application.service' => function(Injector $i, Service $instance) {
$instance->setFoo(new \ArrayObject());
return $instance;
}
];
Using a closure as a decorator has the same pros and cons of using a service factory closure. Once rapid prototyping is done you should create a class that implements Spiffy\Inject\ServiceDecorator
instead of using a closure.
Wrapping services allows you to completely modify every aspect of a service including whether or not the original factory is called. Wrappers are defined by returning an additional array from the config/services.php
file.
// file: application/config/services.php
return [
'foo' => '\StdClass',
'application.service' => ['Application\Service', [], ['setFoo' => '@foo']]
], [
], [
'application.service' => function(Injector $i, $name, $callable) {
// use the callable to use the original service
$instance = $callable();
// or, create a service on our own
// $instance = new Service();
$instance->setFoo(new \ArrayObject());
return $instance;
}
];
Using a closure as a wrapper has the same pros and cons of using a service factory closure. Once rapid prototyping is done you should create a class that implements Spiffy\Inject\ServiceWrapper
instead of using a closure.
© 2014 Kyle Spraggs