Service Definitions
Configuration is the place where we instruct the DI container on how to assemble individual services and how to connect them with other dependencies. Nette provides a very clear and elegant way to achieve this.
The services
section in the NEON configuration file is where we define our custom services and their
configurations. Let's look at a simple example of defining a service named database
, which represents an instance of
the PDO
class:
services:
database: PDO('sqlite::memory:')
This configuration results in the following factory method in the DI container:
public function createServiceDatabase(): PDO
{
return new PDO('sqlite::memory:');
}
Service names allow us to reference them in other parts of the configuration file, using the format @serviceName
.
If there's no need to name the service, we can simply use a bullet point:
services:
- PDO('sqlite::memory:')
To retrieve a service from the DI container, we can use the getService()
method with the service name as a
parameter, or the getByType()
method with the service type:
$database = $container->getService('database');
$database = $container->getByType(PDO::class);
Service Creation
Most commonly, we create a service simply by instantiating a specific class. For example:
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
If we need to expand the configuration with additional keys, the definition can be expanded into multiple lines:
services:
database:
create: PDO('sqlite::memory:')
setup: ...
The create
key has an alias factory
, both versions are common in practice. However, we recommend
using create
.
Constructor arguments or the creation method can alternatively be written in the arguments
key:
services:
database:
create: PDO
arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]
Services don't have to be created just by simple instantiation of a class; they can also result from calling static methods or methods of other services:
services:
database: DatabaseFactory::create()
router: @routerFactory::create()
Note that for simplicity, instead of ->
, we use ::
, see expression
means. These factory methods are generated:
public function createServiceDatabase(): PDO
{
return DatabaseFactory::create();
}
public function createServiceRouter(): RouteList
{
return $this->getService('routerFactory')->create();
}
The DI container needs to know the type of the created service. If we create a service using a method that doesn't have a specified return type, we must explicitly mention this type in the configuration:
services:
database:
create: DatabaseFactory::create()
type: PDO
Arguments
We pass arguments to constructors and methods in a manner very similar to regular PHP:
services:
database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)
For better readability, we can list the arguments on separate lines. In this format, the use of commas is optional:
services:
database: PDO(
'mysql:host=127.0.0.1;dbname=test'
root
secret
)
You can also name the arguments, which then allows you to not worry about their order:
services:
database: PDO(
username: root
password: secret
dsn: 'mysql:host=127.0.0.1;dbname=test'
)
If you wish to omit certain arguments and use their default values or insert a service via autowiring, use an underscore:
services:
foo: Foo(_, %appDir%)
Arguments can be services, parameters, and much more, see expression means.
Setup
In the setup
section, we define the methods that should be called when creating the service.
services:
database:
create: PDO(%dsn%, %user%, %password%)
setup:
- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
In PHP, this would look like:
public function createServiceDatabase(): PDO
{
$service = new PDO('...', '...', '...');
$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
return $service;
}
In addition to method calls, you can also pass values to properties. Adding an element to an array is also supported, but you need to enclose it in quotes to avoid colliding with the NEON syntax:
services:
foo:
create: Foo
setup:
- $value = 123
- '$onClick[]' = [@bar, clickHandler]
In PHP, this would translate to:
public function createServiceFoo(): Foo
{
$service = new Foo;
$service->value = 123;
$service->onClick[] = [$this->getService('bar'), 'clickHandler'];
return $service;
}
In the setup, you can also call static methods or methods of other services. If you need to pass the current service as an
argument, use @self
:
services:
foo:
create: Foo
setup:
- My\Helpers::initializeFoo(@self)
- @anotherService::setFoo(@self)
Note that for simplicity, instead of ->
, we use ::
, see expression
means. This generates the following factory method:
public function createServiceFoo(): Foo
{
$service = new Foo;
My\Helpers::initializeFoo($service);
$this->getService('anotherService')->setFoo($service);
return $service;
}
Expression Means
Nette DI provides us with exceptionally rich expression capabilities, allowing us to articulate almost anything. In configuration files, we can use parameters:
# parameter
%wwwDir%
# value under a parameter key
%mailer.user%
# parameter within a string
'%wwwDir%/images'
We can also create objects, call methods, and functions:
# create an object
DateTime()
# call a static method
Collator::create(%locale%)
# call a PHP function
::getenv(DB_USER)
Refer to services either by their name or by type:
# service by name
@database
# service by type
@Nette\Database\Connection
Use first-class callable syntax:
# creating a callback, equivalent to [@user, logout]
@user::logout(...)
Use constants:
# class constant
FilesystemIterator::SKIP_DOTS
# global constant obtained by the PHP function constant()
::constant(PHP_VERSION)
Method calls can be chained, just like in PHP. For simplicity, instead of ->
, we use ::
:
DateTime()::format('Y-m-d')
# PHP: (new DateTime())->format('Y-m-d')
@http.request::getUrl()::getHost()
# PHP: $this->getService('http.request')->getUrl()->getHost()
These expressions can be used anywhere when creating services, in arguments, in the setup section, or parameters:
parameters:
ipAddress: @http.request::getRemoteAddress()
services:
database:
create: DatabaseFactory::create( @anotherService::getDsn() )
setup:
- initialize( ::getenv('DB_USER') )
Special Functions
Within configuration files, you can utilize these special functions:
not()
for value negationbool()
,int()
,float()
,string()
for lossless type castingtyped()
to generate an array of all services of a specified typetagged()
to create an array of all services with a given tag
services:
- Foo(
id: int(::getenv('ProjectId'))
productionMode: not(%debugMode%)
)
Compared to conventional typecasting in PHP, like (int)
, lossless type casting will throw an exception for
non-numeric values.
The typed()
function creates an array of all services of a particular type (class or interface). It excludes
services with autowiring turned off. Multiple types can be specified, separated by commas.
services:
- BarsDependent( typed(Bar) )
You can also automatically pass an array of services of a specific type as an argument using autowiring.
The tagged()
function creates an array of all services with a specified tag. Multiple tags can be listed,
separated by commas.
services:
- LoggersDependent( tagged(logger) )
Autowiring
The autowired
key allows you to modify the autowiring behavior for a particular service. For more details, see the autowiring chapter.
services:
foo:
create: Foo
autowired: false # the foo service is excluded from autowiring
Tags
Tags are used to add supplementary information to services. You can assign one or more tags to a service:
services:
foo:
create: Foo
tags:
- cached
Tags can also carry values:
services:
foo:
create: Foo
tags:
logger: monolog.logger.event
To retrieve all services with specific tags, you can use the tagged()
function:
services:
- LoggersDependent( tagged(logger) )
In the DI container, you can obtain the names of all services with a specific tag using the findByTag()
method:
$names = $container->findByTag('logger');
// $names is an array containing the service name and tag value
// e.g. ['foo' => 'monolog.logger.event', ...]
Inject Mode
Using the flag inject: true
activates the passing of dependencies via public variables with the inject annotation and inject*() methods.
services:
articles:
create: App\Model\Articles
inject: true
By default, inject
is only activated for presenters.
Service Modifications
The DI container contains many services added either by built-in or user extensions. You can
modify the definitions of these services directly in the configuration. For instance, you can change the class of the
application.application
service, which is conventionally Nette\Application\Application
, to
something else:
services:
application.application:
create: MyApplication
alteration: true
The alteration
flag is informative, indicating that we're merely modifying an existing service.
We can also supplement the setup:
services:
application.application:
create: MyApplication
alteration: true
setup:
- '$onStartup[]' = [@resource, init]
When overwriting a service, you might want to remove original arguments, setup items, or tags, which is where
reset
comes in handy:
services:
application.application:
create: MyApplication
alteration: true
reset:
- arguments
- setup
- tags
If you wish to remove a service added by an extension, you can do so like this:
services:
cache.journal: false