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 valuebool()
,int()
,float()
,string()
lossless casting to the specified typetyped()
creates an array of all services of the specified typetagged()
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