Service Definitions
Configuration is where we place the definitions of custom services. This is done in section
services
.
For example, this is how we create a service named database
, which will be an instance of class
PDO
:
services:
database: PDO('sqlite::memory:')
The naming of services is used to allow us to reference them. If a service is not referenced, there is no need to name it. So we just use a bullet point instead of a name:
services:
- PDO('sqlite::memory:') # anonymous service
A one-line entry can be broken up into multiple lines to allow additional keys to be added, such as setup:
services:
database:
factory: PDO('sqlite::memory:')
setup: ...
We then retrieve the service from the DI container using the method getService()
by name, or better yet, the
method getByType()
by type:
$database = $container->getService('database');
$database = $container->getByType(PDO::class);
Creating a Service
Most often, we create a service by simply creating an instance of a class:
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
Which will generate a factory method in DI container:
public function createServiceDatabase()
{
return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret');
}
Alternatively, a key arguments
can be used to pass arguments:
services:
database:
factory: PDO
arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]
A static method can also create a service:
services:
database: My\Database::create(root, secret)
Corresponds to PHP code:
public function createServiceDatabase()
{
return My\Database::create('root', 'secret');
}
A static method My\Database::create()
is assumed to have a defined return value, either using the
@return
annotation or PHP 7 type hints, that the DI container needs to know. If it does not have it, we write the
type to the configuration:
services:
database:
factory: My\Database::create(root, secret)
type: PDO
Nette DI gives you extremely powerful expression facilities to write almost anything. For example, to refer to another service and call its method. For simplicity, ::
is used instead
of ->
.
services:
routerFactory: App\Router\Factory
router: @routerFactory::create()
Corresponds to PHP code:
public function createServiceRouterFactory()
{
return new App\Router\Factory;
}
public function createServiceRouter()
{
return $this->getService('routerFactory')->create();
}
Method calls can be chained together as in PHP:
services:
foo: FooFactory::build()::get()
Corresponds to PHP code:
public function createServiceFoo()
{
return FooFactory::build()->get();
}
Arguments
Named parameters can also be used to pass arguments:
services:
database: PDO(
'mysql:host=127.0.0.1;dbname=test' # positional
username: root # named
password: secret # named
)
The use of commas is optional when breaking arguments into multiple lines.
Of course, we can also use other services or parameters as arguments:
services:
- Foo(@anotherService, %appDir%)
Corresponds to PHP code:
public function createService01()
{
return new Foo($this->getService('anotherService'), '...');
}
If the first argument is autowired and you want to
specify the second one, omit the first one with the _
character, for example Foo(_, %appDir%)
. Or better
yet, pass only the second argument as a named parameter, e.g. Foo(path: %appDir%)
.
Nette DI and the NEON format give you extremely powerful expressive facilities to write almost anything. Thus an argument can be a newly created object, you can call static methods, methods of other services, or even global functions using special notation:
services:
analyser: My\Analyser(
FilesystemIterator(%appDir%) # create object
DateTime::createFromFormat('Y-m-d') # call static method
@anotherService # passing another service
@http.request::getRemoteAddress() # calling another service method
::getenv(NETTE_MODE) # call a global function
)
Corresponds to PHP code:
public function createServiceAnalyser()
{
return new My\Analyser(
new FilesystemIterator('...'),
DateTime::createFromFormat('Y-m-d'),
$this->getService('anotherService'),
$this->getService('http.request')->getRemoteAddress(),
getenv('NETTE_MODE')
);
}
Referencing Services
Individual services are referenced using character @
and name, so for example @database
:
services:
- factory: Foo(@database)
setup:
- setCacheStorage(@cache.storage)
Corresponds to PHP code:
public function createService01()
{
$service = new Foo($this->getService('database'));
$service->setCacheStorage($this->getService('cache.storage'));
return $service;
}
Even anonymous services can be referenced using a callback, just specify their type (class or interface) instead of their name. However, this is usually not necessary due to autowiring.
services:
- factory: Foo(@Nette\Database\Connection) # or @\PDO
setup:
- setCacheStorage(@cache.storage)
Setup
In the setup section we list the methods to be called when creating the service:
services:
database:
factory: PDO(%dsn%, %user%, %password%)
setup:
- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
Corresponds to PHP code:
public function createServiceDatabase()
{
$service = new PDO('...', '...', '...');
$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $service;
}
Properites can also be set. Adding an element to an array is also supported, and should be written in quotes so as not to conflict with NEON syntax:
services:
foo:
factory: Foo
setup:
- $value = 123
- '$onClick[]' = [@bar, clickHandler]
Corresponds to PHP code:
public function createServiceFoo()
{
$service = new Foo;
$service->value = 123;
$service->onClick[] = [$this->getService('bar'), 'clickHandler'];
return $service;
}
However, static methods or methods of other services can also be called in the setup. We pass the actual service to them as
@self
:
services:
foo:
factory: Foo
setup:
- My\Helpers::initializeFoo(@self)
- @anotherService::setFoo(@self)
Corresponds to PHP code:
public function createServiceFoo()
{
$service = new Foo;
My\Helpers::initializeFoo($service);
$this->getService('anotherService')->setFoo($service);
return $service;
}
Autowiring
The autowired key can be used to exclude a service from autowiring or to influence its behavior. See chapter on autowiring for more information.
services:
foo:
factory: Foo
autowired: false # foo is removed from autowiring
Tags
User information can be added to individual services in the form of tags:
services:
foo:
factory: Foo
tags:
- cached
Tags can also have a value:
services:
foo:
factory: Foo
tags:
logger: monolog.logger.event
Service names can be obtained from the DI container using the method findByTag()
:
$names = $container->findByTag('logger');
// $names is an array containing the service name and tag value
// i.e. ['foo' => 'monolog.logger.event', ...]
Inject Mode
The inject: true
flag is used to activate the passing of dependencies via public variables with the inject annotation and the inject*() methods.
services:
articles:
factory: App\Model\Articles
inject: true
By default, inject
is only activated for presenters.
Modification of Services
There are a number of services in the DI container that have been added by built-in or your
extension. The definitions of these services can be modified in the configuration. For example, for service
application.application
, which is by default an object Nette\Application\Application
, we can change
the class:
services:
application.application:
factory: MyApplication
alteration: true
The alteration
flag is informative and says that we are just modifying an existing service.
We can also add a setup:
services:
application.application:
factory: MyApplication
alteration: true
setup:
- '$onStartup[]' = [@resource, init]
When rewriting a service, we may want to remove the original arguments, setup items or tags, which is what reset
is for:
services:
application.application:
factory: MyApplication
alteration: true
reset:
- arguments
- setup
- tags
A service added by extension can also be removed from the container:
services:
cache.journal: false