Definování služeb

Konfigurace je místem, kam umísťujeme definice vlastních služeb. Slouží k tomu sekce services.

Například takto vytvoříme službu pojmenovanou database, což bude instance třídy PDO:

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

Pojmenování služeb slouží k tomu, abychom se na ně mohli odkazovat. Pokud na službu není odkazováno, není ji potřeba pojmenovávat. Místo názvu tak použijeme jen odrážku:

services:
	- PDO('sqlite::memory:')  #  anonymní služba

Jednořádkový zápis lze rozepsat do více řádků a tak umožnit přidání dalších klíčů, jako je například setup. Aliasem pro klíč create: je factory:.

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

Službu poté získáme z DI kontejneru metodou getService() podle názvu, nebo ještě lépe metodou getByType() podle typu:

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

Vytvoření služby

Nejčastěji službu vytváříme prostým vytvořením instance určité třídy:

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

Což vygeneruje tovární metodu v DI kontejneru:

public function createServiceDatabase(): PDO
{
	return new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret');
}

Pro předání argumentů lze alternativně použít i klíč arguments:

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

Službu může vytvořit také statická metoda:

services:
	database: My\Database::create(root, secret)

Odpovídá PHP kódu:

public function createServiceDatabase(): PDO
{
	return My\Database::create('root', 'secret');
}

Předpokládá se, statická metoda My\Database::create() má definovanou návratovou hodnotu, kterou DI kontejner potřebuje znát. Pokud ji nemá, zapíšeme typ do konfigurace:

services:
	database:
		create: My\Database::create(root, secret)
		type: PDO

Nette DI nám dává mimořádně silné výrazové prostředky, pomocí kterých můžete zapsat téměř cokoliv. Například se odkázat na jinou službu a zavolat její metodu. Pro jednoduchost se místo -> používá ::

services:
	routerFactory: App\Router\Factory
	router: @routerFactory::create()

Odpovídá PHP kódu:

public function createServiceRouterFactory(): App\Router\Factory
{
	return new App\Router\Factory;
}

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

Volání metod lze řetězit za sebe stejně jako v PHP:

services:
	foo: FooFactory::build()::get()

Odpovídá PHP kódu:

public function createServiceFoo()
{
	return FooFactory::build()->get();
}

Argumenty

Pro předání argumentů lze používat i pojmenované parametry:

services:
	database: PDO(
		'mysql:host=127.0.0.1;dbname=test'  # poziční
		username: root                      # pojmenovaný
		password: secret                    # pojmenovaný
	)

Při rozepsání argumentů do více řádků je používání čárek volitelné.

Jako argumenty můžeme samozřejmě použít i jiné služby nebo parametry:

services:
	- Foo(@anotherService, %appDir%)

Odpovídá PHP kódu:

public function createService01(): Foo
{
	return new Foo($this->getService('anotherService'), '...');
}

Pokud se má první argument autowirovat a chceme přitom uvést argument druhý, vynecháme první znakem _, tedy např. Foo(_, %appDir%). Nebo ještě lépe předáme jen druhý argument jako pojmenovaný parametr, např. Foo(path: %appDir%).

Nette DI a formát NEON nám dává mimořádně silné výrazové prostředky, pomocí kterých můžete zapsat téměř cokoliv. Argumentem tak může být nově vytvořený objekt, lze volat statické metody, metody jiných služeb, nebo pomocí speciálního zápisu i globální funkce:

services:
	analyser: My\Analyser(
		FilesystemIterator(%appDir%)         # vytvoření objektu
		DateTime::createFromFormat('Y-m-d')  # volání statické metody
		@anotherService		                 # předání jiné služby
		@http.request::getRemoteAddress()    # volání metody jiné služby
		::getenv(NETTE_MODE)                 # volání globální funkce
	)

Odpovídá PHP kódu:

public function createServiceAnalyser(): My\Analyser
{
	return new My\Analyser(
		new FilesystemIterator('...'),
		DateTime::createFromFormat('Y-m-d'),
		$this->getService('anotherService'),
		$this->getService('http.request')->getRemoteAddress(),
		getenv('NETTE_MODE')
	);
}

Speciální funkce

V argumentech lze také používat speciální funkce pro přetypování nebo negaci hodnot:

  • not(%arg%) negace
  • bool(%arg%) bezeztrátové přetypování na bool
  • int(%arg%) bezeztrátové přetypování na int
  • float(%arg%) bezeztrátové přetypování na float
  • string(%arg%) bezeztrátové přetypování na string
services:
	- Foo(
		id: int(::getenv('PROJECT_ID'))
		productionMode: not(%debugMode%)
	)

Bezztrátové přetypování se od běžného přetypování v PHP např. pomocí (int) liší v tom, že pro nečíselné hodnoty vyhodí výjimku.

Jako argument lze předávat i více služeb. Pole všech služeb určitého typu (tj. třídy nebo rozhraní) vytvoří funkce typed(). Funkce vynechá služby, které mají vypnutý autowiring a lze uvést i více typů oddělených čárkou.

services:
	- BarsDependent( typed(Bar) )

Předávat pole služeb můžete i automaticky pomocí autowiringu.

Pole všech služeb s určitým tagem vytvoří funkce tagged(). Lze uvést i více tagů oddělených čárkou.

services:
	- LoggersDependent( tagged(logger) )

Odkazování na služby

Na jednotlivé služby se odkazuje pomocí zavináče a názvu služby, takže například @database:

services:
	- create: Foo(@database)
	  setup:
			- setCacheStorage(@cache.storage)

Odpovídá PHP kódu:

public function createService01(): Foo
{
	$service = new Foo($this->getService('database'));
	$service->setCacheStorage($this->getService('cache.storage'));
	return $service;
}

I na anonymní služby se lze odkazovat přes zavináč, jen místo názvu uvedeme jejich typ (třídu nebo rozhraní). Tohle ovšem obvykle není potřeba dělat díky autowiringu.

services:
	- create: Foo(@Nette\Database\Connection)  # nebo třeba @\PDO
	  setup:
			- setCacheStorage(@cache.storage)

Setup

V sekci setup uvádíme metody, které se mají zavolat při vytváření služby:

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

Odpovídá PHP kódu:

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

Lze také nastavovat hodnoty proměnných. 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]

Odpovídá PHP kódu:

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 metod jiných služeb. Aktuální službu jim předáme jako @self:

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

Odpovídá PHP kódu:

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

Autowiring

Pomocí klíče autowired lze službu vyřadit z autowiringu nebo jeho chování ovlivnit. Více v kapitole o autowiringu.

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

Tagy

Jednotlivým službám lze přidávat uživatelské informace v podobě tzv. tagů:

services:
	foo:
		create: Foo
		tags:
			- cached

Tagy mohou mít i hodnotu:

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

Pole služeb s určitými tagy lze předat jako argument pomocí funkce tagged(). Lze uvést i více tagů oddělených čárkou.

services:
	- LoggersDependent( tagged(logger) )

Názvy služeb lze získat z DI kontejneru metodou 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

V základním nastavení je inject aktivováno pouze pro presentery.

Modifikace služeb

V DI kontejneru je řada služeb, které přidaly vestavěné nebo vaše rozšíření. Definice těchto služeb lze v konfiguraci pozměnit. Třeba u služby application.application, což je standardně objekt Nette\Application\Application, můžeme změnit třídu:

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

Službu přidanou rozšířením lze také z kontejneru odstranit:

services:
	cache.journal: false