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 negation
  • bool(), int(), float(), string() for lossless type casting
  • typed() to generate an array of all services of a specified type
  • tagged() 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
version: 3.x 2.x