Definování služeb

Konfigurace je místem, kde učíme DI kontejner, jak má sestavovat jednotlivé služby a jak je propojovat s dalšími závislostmi. Nette poskytuje velice přehledný a elegantní způsob, jak toho dosáhnout.

Sekce services v konfiguračním souboru formátu NEON je místem, kde definujeme vlastní služby a jejich konfigurace. Podívejme se na jednoduchý příklad definice služby pojmenované database, která reprezentuje instanci třídy PDO:

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

Uvedená konfigurace vyústí v následující tovární metodu v DI kontejneru:

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

Názvy služeb nám umožňují odkazovat se na ně v dalších částech konfiguračního souboru, a to ve formátu @nazevSluzby. Pokud není potřeba službu pojmenovávat, můžeme jednoduše použít pouze odrážku:

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

Pro získání služby z DI kontejneru můžeme využít metodu getService() s názvem služby jako parametrem, nebo metodu getByType() s typem služby:

$database = $container->getService('database');
$database = $container->getByType(PDO::class);

Vytvoření služby

Většinou vytváříme službu jednoduše tím, že vytvoříme instanci určité třídy. Například:

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

Pokud potřebujeme konfiguraci rozšířit o další klíče, lze definici rozepsat do více řádků:

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

Klíč create má alias factory, obě varianty jsou v praxi běžné. Nicméně doporučujeme používat create.

Argumenty konstruktoru nebo vytvářecí metody mohou být alternativně zapsány v klíči arguments:

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

Služby nemusí být vytvářeny jen prostým vytvořením instance třídy, mohou být také výsledkem volání statických metod nebo metod jiných služeb:

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

Všimněte si, že pro jednoduchost se místo -> používá ::, viz výrazové prostředky. Vygenerují se tyto tovární metody:

public function createServiceDatabase(): PDO
{
	return DatabaseFactory::create();
}

public function createServiceRouter(): RouteList
{
	return $this->getService('routerFactory')->create();
}

DI kontejner potřebuje znát typ vytvořené služby. Pokud vytváříme službu pomocí metody, která nemá specifikovaný návratový typ, musíme tento typ explicitně uvést v konfiguraci:

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

Argumenty

Do konstruktoru a metod předáváme argumenty způsobem velmi podobným jako v samotném PHP:

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

Pro lepší čitelnost můžeme argumenty rozepsat do samostatných řádků. V takovém případě je používání čárek volitelné:

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

Argumenty můžete také pojmenovat a nemusíte se pak starat o jejich pořadí:

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

Pokud chcete některé argumenty vynechat a použít jejich výchozí hodnotu nebo dosadit službu pomocí autowiringu, použijte podtržítko:

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

Jako argumenty lze předávat služby, používat parametry a mnohem více, viz výrazové prostředky.

Setup

V sekci setup definujeme metody, které se mají volat při vytváření služby.

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

To by v PHP vypadalo takto:

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

Kromě volání metod lze také předávat hodnoty do properties. Podporováno je i přidání prvku do pole, které je potřeba zapsat v uvozovkách, aby nekolidovalo se syntaxí NEON:

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

Což by v PHP kódu vypadalo následovně:

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

V setupu lze však volat i statické metody nebo metody jiných služeb. Pokud potřebujete předat jako argument aktuální službu, uveďte ji jako @self:

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

Všimněte si, že pro jednoduchost se místo -> používá ::, viz výrazové prostředky. Vygeneruje se taková tovární metoda:

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

Výrazové prostředky

Nette DI nám dává mimořádně bohaté výrazové prostředky, pomocí kterých můžeme zapsat téměř cokoliv. V konfiguračních souborech tak můžeme využívat parametry:

# parametr
%wwwDir%

# hodnota parametru pod klíčem
%mailer.user%

# parametr uvnitř řetězce
'%wwwDir%/images'

Dále vytvářet objekty, volat metody a funkce:

# vytvoření objektu
DateTime()

# volání statické metody
Collator::create(%locale%)

# volání PHP funkce
::getenv(DB_USER)

Odkazovat se na služby buď jejich jménem nebo pomocí typu:

# služba dle názvu
@database

# služba dle typu
@Nette\Database\Connection

Používat first-class callable syntax:

# vytvoření callbacku, obdoba [@user, logout]
@user::logout(...)

Používat konstanty:

# konstanta třídy
FilesystemIterator::SKIP_DOTS

# globální konstantu získáme PHP funkcí constant()
::constant(PHP_VERSION)

Volání metod lze řetězit stejně jako v PHP. Jen pro jednoduchost se místo -> používá :::

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

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

Tyto výrazy můžete používat kdekoliv, při vytváření služeb, v argumentech, v sekci setup nebo parametrech:

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

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

Speciální funkce

V konfiguračních souborech můžete používa tyto speciální funkce:

  • not() negace hodnoty
  • bool(), int(), float(), string() bezeztrátové přetypování na daný typ
  • typed() vytvoří pole všech služeb specifikovaného typu
  • tagged() vytvoření pole všech služeb s daným tagem
services:
	- Foo(
		id: int(::getenv('ProjectId'))
		productionMode: not(%debugMode%)
	)

Oproti klasickému přetypování v PHP, jako je např. (int), bezeztrátové přetypování vyhodí výjimku pro nečíselné hodnoty.

Funkce typed() vytvoří pole všech služeb daného typu (třída nebo rozhraní). Vynechá služby, které mají vypnutý autowiring. Lze uvést i více typů oddělených čárkou.

services:
	- BarsDependent( typed(Bar) )

Pole služeb určitého typu můžete předávat jako argument také automaticky pomocí autowiringu.

Funkce tagged() pak vytváří pole všech služeb s určitým tagem. I zde můžete specifikovat více tagů oddělených čárkou.

services:
	- LoggersDependent( tagged(logger) )

Autowiring

Klíč autowired umožňuje ovlivnit chování autowiringu pro konkrétní službu. Pro detaily viz kapitola o autowiringu.

services:
	foo:
		create: Foo
		autowired: false     # služba foo je vyřazena z autowiringu

Tagy

Tagy slouží k přidání doplňujících informací k službám. Službě můžete přidat jeden nebo více tagů:

services:
	foo:
		create: Foo
		tags:
			- cached

Tagy mohou také nést hodnoty:

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

Aby jste získali všechny služby s určitými tagy, můžete použít funkci tagged():

services:
	- LoggersDependent( tagged(logger) )

V DI kontejneru můžete získat názvy všech služeb s určitým tagem pomocí metody findByTag():

$names = $container->findByTag('logger');
// $names je pole obsahující název služby a hodnotu tagu
// např. ['foo' => 'monolog.logger.event', ...]

Režim Inject

Pomocí příznaku inject: true se aktivuje předávání závislostí přes veřejné proměnné s anotací inject a metody inject*().

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

Ve výchozím nastavení je inject aktivováno pouze pro presentery.

Modifikace služeb

DI kontejner obsahuje mnoho služeb, které byly přidány prostřednictvím vestavěného nebo uživatelského rozšíření. Můžete upravit definice těchto služeb přímo v konfiguraci. Například můžete změnit třídu služby application.application, což je standardně Nette\Application\Application, na jinou:

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

Příznak alteration je informativní a říká, že jen modifikujeme existující službu.

Můžeme také doplnit setup:

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

Při přepisování služby můžeme chtít odstranit původní argumenty, položky setup nebo tagy, k čemuž slouží reset:

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

Pokud chcete odstranit službu přidanou rozšířením, můžete to udělat takto:

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