Service Definitions

Configuration is where we instruct the DI container how to create individual services and how to connect them with their dependencies. Nette offers a very clear and elegant way to achieve this.

The services section in the NEON configuration file is where we define our own services and their configurations. Let's look at a simple example defining a service named database, which represents an instance of the PDO class:

services:
	database: PDO('sqlite::memory:')

The configuration above results in the following factory method in the DI container:

public function createServiceDatabase(): PDO
{
	return new PDO('sqlite::memory:');
}

Service names enable referencing them in other parts of the configuration file, using the @serviceName format. If there's no need to assign a name to 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

Usually, 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 split across multiple lines:

services:
	database:
		create: PDO('sqlite::memory:')
		setup: ...

The create key has an alias factory; both variants are commonly used. However, we recommend using create.

Arguments for the constructor or the factory method can alternatively be specified using the arguments key:

services:
	database:
		create: PDO
		arguments: ['mysql:host=127.0.0.1;dbname=test', root, secret]

Services don't necessarily have to be created by simple class instantiation; they can also be the result of invoking static methods or methods of other services:

services:
	database: DatabaseFactory::create()
	router: @routerFactory::create()

Note that for simplicity, :: is used instead of ->, see Expression Language. These factory methods will be 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 service being created. If we create a service using a method that lacks a specified return type, we must explicitly declare this type in the configuration:

services:
	database:
		create: DatabaseFactory::create()
		type: PDO

Arguments

We pass arguments to constructors and methods in a way very similar to how it's done in PHP itself:

services:
	database: PDO('mysql:host=127.0.0.1;dbname=test', root, secret)

For better readability, we can list arguments on separate lines. In this case, using commas becomes optional:

services:
	database: PDO(
		'mysql:host=127.0.0.1;dbname=test'
		root
		secret
	)

You can also name the arguments, eliminating the need to worry about their order:

services:
	database: PDO(
		username: root
		password: secret
		dsn: 'mysql:host=127.0.0.1;dbname=test'
	)

If you want to omit certain arguments and use their default values or have a service injected via autowiring, use an underscore (_):

services:
	foo: Foo(_, %appDir%)

Arguments can include services, parameters, and much more, see Expression Language.

Setup

In the setup section, we define methods that should be invoked upon service creation.

services:
	database:
		create: PDO(%dsn%, %user%, %password%)
		setup:
			- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)

This would look like this in PHP:

public function createServiceDatabase(): PDO
{
	$service = new PDO('...', '...', '...');
	$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
	return $service;
}

In addition to method calls, values can also be assigned to properties. Adding elements to arrays is also supported, which requires enclosing the array access in quotes to avoid conflicts with NEON syntax:

services:
	foo:
		create: Foo
		setup:
			- $value = 123
			- '$onClick[]' = [@bar, clickHandler]

Which would look like the following in PHP code:

public function createServiceFoo(): Foo
{
	$service = new Foo;
	$service->value = 123;
	$service->onClick[] = [$this->getService('bar'), 'clickHandler'];
	return $service;
}

However, in the setup, you can also invoke static methods or methods of other services. If you need to pass the current service itself as an argument, refer to it using @self:

services:
	foo:
		create: Foo
		setup:
			- My\Helpers::initializeFoo(@self)
			- @anotherService::setFoo(@self)

Note that for simplicity, :: is used instead of ->, see Expression Language. Such a factory method will be generated:

public function createServiceFoo(): Foo
{
	$service = new Foo;
	My\Helpers::initializeFoo($service);
	$this->getService('anotherService')->setFoo($service);
	return $service;
}

Expression Language

Nette DI provides an exceptionally rich expression language, through which we can define almost anything. In configuration files, we can thus use parameters:

# parameter
%wwwDir%

# value of a parameter under a key
%mailer.user%

# parameter inside a string
'%wwwDir%/images'

Furthermore, create objects, call methods and functions:

# create object
DateTime()

# call static method
Collator::create(%locale%)

# call 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:

# create callback, equivalent to [@user, logout]
@user::logout(...)

Use constants:

# class constant
FilesystemIterator::SKIP_DOTS

# get global constant using PHP function constant()
::constant(\PHP_VERSION)

Method calls can be chained just like in PHP. For simplicity, :: is used instead of ->:

DateTime()::format('Y-m-d')
# PHP: (new DateTime())->format('Y-m-d')

@http.request::getUrl()::getHost()
# PHP: $this->getService('http.request')->getUrl()->getHost()

You can use these expressions anywhere, when creating services, in arguments, in the setup section, or in parameters:

parameters:
	ipAddress: @http.request::getRemoteAddress()

services:
	database:
		create: DatabaseFactory::create( @anotherService::getDsn() )
		setup:
			- initialize( ::getenv('DB_USER') )

Special Functions

In configuration files, you can use the following special functions:

  • not() negates a value
  • bool(), int(), float(), string() lossless casting to the specified type
  • typed() creates an array of all services of the specified type
  • tagged() creates an array of all services with the given tag
services:
	- Foo(
		id: int(::getenv('ProjectId'))
		productionMode: not(%debugMode%)
	)

Unlike standard PHP casting, such as (int), lossless casting throws an exception for non-numeric values.

The typed() function creates an array of all services of the specified type (class or interface). It excludes services that have autowiring disabled. Multiple types can also be specified, separated by commas.

services:
	- BarsDependent( typed(Bar) )

An array of services of a certain type can also be passed as an argument automatically using autowiring.

The tagged() function then creates an array of all services with a specific tag. Here too, you can specify multiple tags separated by commas.

services:
	- LoggersDependent( tagged(logger) )

Autowiring

The autowired key allows you to influence the autowiring behavior for a specific service. For details, see the chapter on autowiring.

services:
	foo:
		create: Foo
		autowired: false     # the foo service is excluded from autowiring

Lazy Services

Lazy loading is a technique that defers the creation of a service until it is actually needed. In the global configuration, you can enable lazy creation for all services at once. For individual services, you can then override this behavior:

services:
	foo:
		create: Foo
		lazy: false

When a service is defined as lazy, upon requesting it from the DI container, we receive a special proxy object. This proxy looks and behaves identically to the actual service, but the actual initialization (constructor invocation and setup calls) occurs only upon the first access to any of its methods or properties.

Lazy loading can only be used for user-defined classes, not for internal PHP classes. It requires PHP 8.4 or newer.

Tags

Tags serve 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 hold values:

services:
	foo:
		create: Foo
		tags:
			logger: monolog.logger.event

To retrieve all services associated with specific tags, you can use the tagged() function:

services:
	- LoggersDependent( tagged(logger) )

Within the DI container, you can retrieve the names of all services with a specific tag using the findByTag() method:

$names = $container->findByTag('logger');
// $names is an array containing service names as keys and tag values as values
// e.g., ['foo' => 'monolog.logger.event', ...]

Inject Mode

Using the inject: true flag enables dependency injection via public properties annotated with inject and inject*() methods.

services:
	articles:
		create: App\Model\Articles
		inject: true

By default, inject mode is enabled only for presenters.

Service Modifications

The DI container holds numerous services added via built-in or user extensions. You can modify the definitions of these existing services directly in the configuration. For example, you can change the class for the application.application service, which defaults to Nette\Application\Application, to a different one:

services:
	application.application:
		create: MyApplication
		alteration: true

The alteration flag is informative, indicating that we are merely modifying an existing service.

We can also supplement the setup:

services:
	application.application:
		create: MyApplication
		alteration: true
		setup:
			- '$onStartup[]' = [@resource, init]

When modifying a service, we might want to remove original arguments, setup items, or tags, using the reset key:

services:
	application.application:
		create: MyApplication
		alteration: true
		reset:
			- arguments
			- setup
			- tags

If you want to remove a service added by an extension, you can do so as follows:

services:
	cache.journal: false
version: 3.x 2.x