DI: Služby

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

Například takto může vypadat definice služby pojmenované database, což bude instance PDO:

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

Pojďme se podívat na zápis podrobněji:

services:
	# v jednom řádku
	database: PDO(%dsn%, %user%, %password%)

	# nebo ve dvou řádcích:
	database:
		factory: PDO(%dsn%, %user%, %password%)

	# s názvy parametrů (na pořadí nezáleží)
	database:
		factory: PDO(dsn: %dsn%, username: %user%)

	# nebo ve třech řádcích :-)
	database:
		factory: PDO
		arguments: [%dsn%, %user%, %password%]

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

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

Kromě vytvoření instance třídy lze volat i metodu:

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

Vygenerovaný kód:

function createServiceDatabase()
{
	$service = Database::create('root', 'secret');
	return $service;
}

V tomto případě je nutné, aby metoda Database::create() měla definovaný návratový typ buď pomocí anotace @return nebo type hintů v PHP 7.

Službu získáme z DI kontejneru metodou getService():

$database = $container->getService('database');

Setup

Nad vytvořenou službou můžeme volat metody nebo nastavovat hodnoty proměnným a statickým proměnným:

services:
	database:
		factory: PDO(%dsn%, %user%, %password%)
		setup:
			- setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION)
			- $mode = 123
			- '$items[]' = 456

	myService:
		factory: MyService
		setup:
			- MyService::$foo = 2

Vygeneruje:

public function createServiceDatabase()
{
	$service = new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret');
	$service->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
	$service->mode = 123;
	$service->items[] = 456;
	return $service;
}

public function createServiceMyService()
{
	$service = new MyService;
	MyService::$foo = 2;
	return $service;
}

Parametry metod mohou být kromě řetězců a čísel i pole, lze vytvářet i nové objekty nebo volat metody:

services:
	analyser: My\Analyser(
		FilesystemIterator(%appDir%)
		[dryrun: true, verbose: false]
		DateTime::createFromFormat('Y-m-d')
	)

Vygeneruje:

public function createServiceAnalyser()
{
	return new My\Analyser(
		new FilesystemIterator('...'),
		['dryrun' => true, 'verbose' => false],
		DateTime::createFromFormat('Y-m-d')
	);
}

Odkazování na služby

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

services:
	database: PDO(%dsn%, %user%, %password%)
	articles:
		factory: Model\ArticleRepository(@database)
		setup:
			- setCacheStorage(@cache.storage)   # cache.storage je systémová služba

Vygeneruje:

public function createServiceArticles()
{
	$service = new Model\ArticleRepository($this->getService('database'));
	$service->setCacheStorage($this->getService('cache.storage'));
	return $service;
}

I na anonymní služby 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:
	articles:
		factory: Model\ArticleRepository(@Nette\Database\Connection)  # nebo @\PDO

Anonymní služby

Pojmenování služeb je vhodné hlavně ve chvíli, kdy na ně chceme odkazovat z jiných částí konfiguračního souboru. Pokud na službu není odkazováno názvem, není ji potřeba pojmenovávat. Pro anonymní služby použijte následující syntax:

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

	-
		factory: Model\ArticleRepository
		setup:
			- setCacheStorage

Službu získáme z DI kontejneru metodou getByType():

$database = $container->getByType(PDO::class);
$repository = $container->getByType(Model\ArticleRepository::class);

Pokročilá syntaxe

Formát NEON nám dává mimořádně silné výrazové prostředky, pomocí kterých můžete zapsat téměř cokoliv.

Nad odkazovanou službou lze volat metody, nicméně pro jednoduchost místo -> používáme ::.

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

Vygeneruje:

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

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

Volání method lze zřetězit za sebe:

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

Vygeneruje:

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

Volání metod lze použít i v parametrech. Krom metod je lze volat i globální funkce, před jejich názvem dáme čtyřtečku:

services:
	routerFactory: App\Router\Factory( Foo::bar() )   # volání statické metody
	setup:
		- setIp( @http.request::getRemoteAddress() )  # http.request je systémová služba
		- setMode( ::getenv(NETTE_MODE) )             # global function getenv

Vygeneruje:

public function createServiceRouterFactory()
{
	$service = new App\Router\Factory( Foo::bar() );
	$service->setIp( $this->getService('http.request')->getRemoteAddress() );
	$service->setMode( getenv('NETTE_MODE') );
	return $service;
}

Zápis ve tvaru NázevTřídy([parametry, ...]), který používáme obvykle v položce factory a který znamená vytvoření objektu, vlastně odpovídá zápisu v PHP jen s tím rozdílem, že vynecháváme operátor new. Tento zápis můžeme používat i kdekoliv jinde, třeba jako parametr:

services:
	articles:
		factory: Model\ArticleRepository( PDO(%dsn%, %user%, %password%) )
		setup:
			- setCacheStorage( Nette\Caching\Storages\DevNullStorage() )

Vygeneruje:

public function createServiceArticles()
{
	$service = new Model\ArticleRepository( new PDO('mysql:host=127.0.0.1;dbname=test', 'root', 'secret') );
	$service->setCacheStorage( new Nette\Caching\Storages\DevNullStorage );
	return $service;
}

Dokonce nad vytvořeným objektem můžeme rovnou volat metody:

services:
	router: App\Router\Factory()::create()
	# pozor, neplést s App\Router\Factory::create()

Vygeneruje:

public function createServiceRouter()
{
	return (new App\Router\Factory())->create();
}

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:
		factory: 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:
		factory: 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:
		factory: MyApplication
		alteration: true
		setup:
			- $onStartup = [@resource::init]

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

services:
	cache.journal: false