[2011–12–12] Přechod na nový Configurator v poslední verzi

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
David Grudl
Nette Core | 8227
+
0
-

[Aktualizováno] V poslední beta verzi (po které již bude následovat RC), byl značně vylepšen Configurator, což je třída, která má za úkol vytvořit systémový DI kontejner a načíst konfiguraci ze souboru. Kód by měl být čitelnější, rychlejší a samotný Configurator je plně rozšiřitelný. Protože změny nebylo možné udělat s plným zachováním zpětné kompatibility, uvádím tuto kuchařku, jak aplikace aktualizovat.

Když vám cokoliv nebude fungovat, dejte vědět.

Třída Configurator

Třída Nette\Configurator byla přejmenována na Nette\Config\Configurator. Po jejím vytvoření hned uveďte cestu k adresáři temp:

// dříve
$configurator = new Nette\Configurator;
$configurator->container->params['tempDir'] = __DIR__ . '/../temp';

// nyní
$configurator = new Nette\Config\Configurator;
$configurator->setTempDirectory(__DIR__ . '/../temp');

Produkční / developer režim

Detekce režimu se zpřísnila, vývojářský režim se detekuje jen pro IP adresu 127.0.0.1. Produkční režim nastavíte nebo zrušíte metodou setProductionMode(), pro zjištění slouží isProductionMode().

Parametry

Metody createContainer() vygeneruje DI kontejner, konfigurační soubor (nebo soubory) vložíte metodou addConfig($file, [$section]). Parametry předejte metodou addParameter():

// dříve
$configurator->container->params += $params;

// nyní
$configurator->addParameters($params);

RobotLoader

RobotLoader je potřeba startovat ještě dříve, než se načte konfigurační soubor. Můžete to udělat ručně přímo vytvořením instance RobotLoaderu nebo využít předpřipravenou metodu createRobotLoader():

// dříve v configu:
services:
	robotLoader:
		run: true

nyní v bootstrap.php:

$configurator->createRobotLoader()
	->addDirectory(APP_DIR)
	->addDirectory(LIBS_DIR)
	->register();

Konfigurační soubor

Konfigurační soubor můžete být uložen krom formátů NEON a INI nově i v čistém PHP poli.

Podporované je rozdělení na klasické sekce common, development, production, které však lze vypnout uvedením konstanty  Configurator::NONE jakožto druhého parametru addConfig().

Podporované podsekce jsou php, services, factories, parameters, constants, includes, jednotná čísla jako service či starší mode, const, variable nejsou již podporované. Protože další sekce konfiguračního souboru jsou určeny pro zpracovány vlastním handlerem, instancí Nette\Config\CompilerExtension, přesuňte jejich obsah pod parameters. (výjimka Found sections ‚database‘ in configuration, but corresponding extensions are missing.)

Syntaxe NEON byla rozšířena tak, že identifikátory se zavináčem není třeba uzavírat do uvozovek, a dále o tzv. Neon entity ve tvaru name(item, item).

Rozdělení do více konfiguračních souborů

Novinkou je možnost rozdělení konfigurace do více souborů. Můžete vícekrát volat metodu addConfig() nebo soubory uvést v sekci includes konfiguračního souboru. Uumístěte ji (nebo je) pod hlavní sekce jako development nebo production, pokus je používáte, nicméně v inkludovaných souborech se už tyto sekce nepoužívají:

common:
	includes:
	    services.neon
	    parameters.php

Každý z inkludovaných souborů může být v jiném formátu a taktéž může inkludovat další soubory.

Auto-wiring

Auto-wiring umí automaticky předávat do konstruktoru a dalších metod požadované služby. Použije se tehdy, pokud argument neuvedeme nebo pokud uvedeme tři tečky .... Řídí se především podle type hintů a také anotací @return. Služba odpovídající hledané třídě musí být v kontejneru právě jedna, jinak se vyhodí výjimka.

Pokud přidáte novou službu, která způsobí kolizi (například další cacheStorage, výjimka Multiple preferred services of type $class found), uveďte u něj autowired: no a tato bude vynechána z auto-wiringu. (Tj. nebude nabízena při auto-wiringu, ale samotná může auto-wiring používat).

Konfigurace služeb

V definici služeb nahradilo methods obecnější setup, kterým lze volat jakýkoliv cíl nebo nastavovat proměnné. Ty lze elegantně zapisovat pomocí Neon entit. Lze jimi volitelně nahradit i arguments:

// dříve:
	services:
		database:
			class: Nette\Database\Connection
			arguments: ['mysql:host=localhost', 'user', 'password']
			methods:
				- [setCacheStorage, ['@cacheStorage']]

// nyní:
	services:
		database:
			class: Nette\Database\Connection('mysql:host=localhost', user, password)
			setup:
				- setCacheStorage(@cacheStorage)

Díky auto-wiringu není potřeba odkazy na jednoznačné služby uvádět a stačí:

			setup:
				- setCacheStorage

Jako argument lze použít objekt nebo volání funkce:

services:
        notorm: NotORM(PDO('mysql:host=localhost;dbname=database', user, password))
		# return new NotORM( new PDO(....), ...)

	authenticator: MyAuth(@database::table(users))
		# return new MyAuth( $this->getService('database')->table('users') )

Pokud vám službu vyrábí továrnička, můžete třídu uvést v položce class, tj. bude existovat zároveň class i factory. Pokud třída uvedena nebude, zkusí ji auto-wiring dohledat v anotaci @return továrničky. Továrničce se již automaticky nepředává jako první parametr DI kontejner – nicméně pokud má správně uvedený typehint Nette\DI\Container, bude předán auto-wiringem. Další parametry lze uvést v poli arguments nebo pomocí zápisu NEON entity:

	services:
		database:
			factory: MyFactory::createDatabase(..., %dns%, %user%, %pass%)

Na místo trojtečky bude služba doplněna auto-wiringem, nebo lze uvést kontejner pomocí @container.

Autostart session

Session se nestartuje automagicky při startu Application, pokud ji chcete startovat, udělejte tak v bootstrapu.

Factories, dědičnost, entity:

Dalšími novinkami jsou factories (narozdíl od services se při každém volání vytvoří instance, odpovídá createArticledokumentace DI, dědičnost služeb je popsána v rodící se dokumentaci konfigurátoru.

Patrik Votoček
Člen | 2221
+
0
-

Neměl by se config soubor v sandboxu jemnovat trochu jinak?
Varianty:

  • app/config/app.neon
  • app/config/main.neon
  • app/config/default.neon
Fanda
Člen | 39
+
0
-

Jak je teď možný použít statický container (https://doc.nette.org/…introduction#…

Zřejmě je možné includovat soubory minimálně 2, protože když potřebuju (zadám) jen jeden, parsuje se jako string místo array.

Editoval Fanda (20. 12. 2011 1:41)

Fanda
Člen | 39
+
0
-

David Grudl napsal(a):

To, co je popsané v dokumentaci DI, by mělo fungovat. Taky Configurator vytváří statický kontejner. Ale nejspíš nerozumím otázce.

Asi jsem se špatně vyjádřil. Dříve jsem si podědil Nette\DI\Container, přidal vlastní služby a v bootstrap ho předal konstruktorem configuratoru. Teď když koukám do třídy Configurator, nevidím způsob, jak mu předat vlastní container. Asi jen ale nechápu širší souvislosti.

Filip Procházka
Moderator | 4668
+
0
-

@**Fanda**: to je „bad practise“ :) Použíj configy.

Fanda
Člen | 39
+
0
-

@HosipLan: A proč bad practise? Totéž přece udělá configurator, jen nad tím mám menší kontrolu. Taky se programátor musí (více) učit další syntax (config). Osobně raději aplikace programuju, než konfiguruju a služby a továrny mi fakt přijdou logický spíš v kódu. Připouštím ale, že nevidím tak daleko, jak asi vidíte vy. :-P Každopádně mi popsaný způsob vyhovoval daleko víc.

Patrik Votoček
Člen | 2221
+
0
-

Fanda napsal(a):

Asi jsem se špatně vyjádřil. Dříve jsem si podědil Nette\DI\Container, přidal vlastní služby a v bootstrap ho předal konstruktorem configuratoru. Teď když koukám do třídy Configurator, nevidím způsob, jak mu předat vlastní container. Asi jen ale nechápu širší souvislosti.

Musíš si udělat vlastní Nette\Config\CompilerExtension a udělat si metodu Nette\Config\CompilerExtension::afterCompile.

class MyExtension extends \Nette\Config\CompilerExtension
{
	/**
	 * Adjusts DI container compiled to PHP class.
	 * @return void
	 */
	public function afterCompile(Nette\Utils\PhpGenerator\ClassType $class)
	{
		$class->addExtend('MySuperExtraContainer');
	}

	public static function register($configurator)
	{
		$configurator->onCompile[] = function(\Nette\Config\Configurator $configurator, \Nette\Config\Compiler $compiler) {
			$compiler->addExtension('myContainer', new static);
		};
	}
}

Pak před $container = $configurator->createContainer(); dát MyExtension::register($configurator).

Jiný způsob jsem nenašel…

Nicméně v tak jak je nový DIc postavený by jsi věc jako statický kontejner neměl vůbec potřebovat. :-)

Fanda
Člen | 39
+
0
-

HosipLan napsal(a):

@**Fanda** určitě chtěl mít vlastní protected function createServiceMyService metody v containeru místo v configu.

Tak jsem to myslel. Děkuji, že mi rozumíš. :-) Vidím, že nette jde cestou konfigurování, tak se přizpůsobím. Samotné konfigurování mi tak moc problém nedělá, spíš ideově nesouhlasím. :-)

Patrik Votoček
Člen | 2221
+
0
-

Btw to mě tak napadá jde nějak pomocí Nette\Config\CompilerExtension definovat vlastní výchozá parameters docela by se mě to hodilo?

Filip Procházka
Moderator | 4668
+
0
-

Máš v loadConfig přístupný public ContainerBuilder::$parameters, který se expanduje do Containeru. Zkusil bych to tudy.

Patrik Votoček
Člen | 2221
+
0
-

Ha Ivan! Dík!

Editoval Patrik Votoček (20. 12. 2011 16:49)

David Grudl
Nette Core | 8227
+
0
-

API CompilerExtension není definitivní, nicméně je to poslední věc, do které plánuju v DI případně zasahovat.

David Grudl
Nette Core | 8227
+
0
-

Změnit, z čeho dědí SystemContainer, už lze pomocí parametru:

parameters:
	container:
		parent: JinaTrida

nebo

$configurator->addParameters(array(
	'container' => array(
		'parent' => 'JinaTrida'
	),
));
klip
Člen | 11
+
0
-

Poznámka ke config.neon ze sandboxu, PostgreSQL a DiscoveryReflection:
https://forum.nette.org/…ryreflection#…

Čelo
Člen | 42
+
0
-

Podporované je rozdělení na klasické sekce common, development, production, které však lze vypnout uvedením konstanty Configurator::NONE jakožto druhého parametru addConfig().

Trošku mne zmátlo, že v sandboxu je „console < common“. Snažil jsem se aktualizovat aplikaci z dřívější verze a chvíli jsem zmateně hledal, proč mi nefunguje načítání dat z tohoto prostředí. Porovnával jsem změny právě podle sandboxu. To je tedy konec tomuto prostředí?

David Grudl
Nette Core | 8227
+
0
-

Ano, je to tak, proti této vlastnosti byla řada námitek.

Patrik Votoček
Člen | 2221
+
0
-

Takže pár věcí na které jsem narazil:

pokud jsem to dobře pochopil nelze definovat factory a službu se stejným jménem

Use case: chci na něco mít továrničku a zároveň chci mít možnost výchozí služby

class MyExtension extends \Nette\Config\CompilerExtension
{
	public function loadConfiguration(\Nette\DI\ContainerBuilder $container, array $config)
	{
		// foo factory
		$container->addDefinition($this->prefix('foo'))
			->setClass('Foo(%arg1%, %arg2%)')
			->setParameters(array('arg1', array('arg2' => NULL)))
			->setShared(FALSE);

		// foo from factory
		$container->addDefinition($this->prefix('foo'))
			->setClass('Foo')
			->setFactory('@container::createFoo', array(
				$this->prefix('@bar'), '%config%'
			));
	}
}

Kód nahoře mě přivádí k další otázce: Používám dobře prefix? Nikde jsem nenašel jeho využití.

A poslení otázka může mít továrnička definovanou factory? (vzásadě by to byl alias pro někde hluboko zanořenou továrničku mimo kontainer)

Editoval Patrik Votoček (23. 12. 2011 8:33)

David Grudl
Nette Core | 8227
+
0
-

pokud jsem to dobře pochopil nelze definovat factory a službu se stejným jménem

Je to kvůli jednoduchosti konfigurace, aby se na obojí dalo odkazovat přes @name a nedocházelo ke konfliktům.

Use case: chci na něco mít továrničku a zároveň chci mít možnost výchozí služby

Nazvi službu defaultFoo. Asi cítíš, že není úplně ok, pokud $container->createFoo() a $container->getFoo() vrací něco jiného, zatímco $container->getDefaultFoo() je srozumitelné. (jasně, container má getService).

Kód nahoře mě přivádí k další otázce: Používám dobře prefix? Nikde jsem nenašel jeho využití.

Ano, ale chybí prefix u setFactory a špatně je zapsán setClass a setParameters. Lépe by bylo takto:

		$foo = $container->addDefinition($this->prefix('foo'))
			->setClass('Foo', array('%arg1%', '%arg2%'))
			->setParameters(array('arg1', 'arg2' => NULL));

		$container->addDefinition($this->prefix('defaultFoo'))
			->setClass('Foo') // tohle by se asi dalo vynechat
			->setFactory($foo, array($bar, '%config%'));

A poslení otázka může mít továrnička definovanou factory? (vzásadě by to byl alias pro někde hluboko zanořenou továrničku mimo kontainer)

Nerozumím. Továrnička = factory in english.

Patrik Votoček
Člen | 2221
+
0
-

David Grudl napsal(a):

Nerozumím. Továrnička = factory in english.

myslel jsem tím něco takového:

$container->addDefinition($this->prefix('foo'))
	->setClass('Foo')
	->setFactory('@bar::create', array('%arg1%', '%arg2%'))
	->setParameters(array('arg1', 'arg2' => NULL))
	->setShared(FALSE);

vygeneruje:

public function createPrefix_Foo($arg1, $arg2 = NULL)
{
	return $this->getService('bar')->create($arg1, $arg2);
}

Edit případně:

$container->addDefinition($this->prefix('foo'))
	->setClass('Foo')
	->setFactory('Foo::create', array('%arg1%', '%arg2%'))
	->setParameters(array('arg1', 'arg2' => NULL))
	->setShared(FALSE);

vygeneruje:

public function createPrefix_Foo($arg1, $arg2 = NULL)
{
	return Foo::create($arg1, $arg2);
}

Editoval Patrik Votoček (23. 12. 2011 8:39)

David Grudl
Nette Core | 8227
+
0
-

Přesně tak.

Patrik Votoček
Člen | 2221
+
0
-

Tak to je Cool!

Ještě mě napadlo…

$ext->prefix('@container::createFoo'); // @prefix_container::createFoo

němělo by to spíš vyhodit @container::createPrefix_Foo (container je přecejenom takový zvláštní případ).

David Grudl
Nette Core | 8227
+
0
-

Použij $ext->prefix('@foo');

Proki
Člen | 66
+
0
-

V aktuální verzi Nette používám v presenterech pro přístup k parametrům v config.neon syntaxi $this->context->parameters['foo']. Metoda getContext() presenteru je ale označena jako deprecated. Proto se chci zeptat, zda existuje nějaký novější a v budoucnu podporovaný přístup, jak se dostat k těmto parametrům. Nebo je to obecně chyba a presentery na tyto parametry šahat nemají?

bojovyletoun
Člen | 667
+
0
-

Mám takovou potíž s DI kontejnerem. Jde o to, že do hotového containeru se při přidání služby do property $classes kontejneru sám nepřidá záznam spojující interface=>název služby. Což pak samozřejmě vyhodí new MissingServiceException("Service of type $class not found."), pokud volám $presenter->user->login(). Samozřejmě moje řešení je buď přidat ten název ručně, jak je uvedeno v 1) nebo přidat authenticatora do user pomocí 2)

Mě tedy spíš zajímá, jestli by nestálo (a bylo by to správně) za to při přidání služby pomocí addservice či __set aktualizovat $classes automaticky.

Pozn. funkci reset se nelíbí, že dostává hodnotu, navíc jsem neřešil, když třída impl. víc inerfaces.

$cfg = new Nette\Configurator();
$cfg->tempDirectory = $tmp;
$cont = $cfg->createContainer();
$name='authenticator';
$svc = new SimpleAuthenticator(array('admin' => 'pepa123'));
$cont->addService($name, $svc);
//1 tohle musím zařídit ručně
$cont->classes[strtolower(reset(class_implements($svc)))]=$name;
//nebo 2)
$cont->user->authenticator = $svc;
Filip Procházka
Moderator | 4668
+
0
-

To je velice nedostatečné řešení. Pokud potřebuješ vytvořit instanci mimo kontejner, stejně tu službu tam nastav.

services:
	foo:
		class: Foo

a pak si tam klidně vnuť vlastní instanci

$container->addService('foo', $foo);

Container bude správně zkompilovaný, jenom mu instanci vytvoříš ty sám. Díky tomu ti bude fungovat doplňování atd. Jenom bacha, když tam dáš instanci jiné třídy, Container to nebude kontrolovat. Takže opatrně ;)

Patrik Votoček
Člen | 2221
+
0
-

David Grudl napsal(a):

		$foo = $container->addDefinition($this->prefix('foo'))
			->setClass('Foo', array('%arg1%', '%arg2%'))
			->setParameters(array('arg1', 'arg2' => NULL));

		$container->addDefinition($this->prefix('defaultFoo'))
			->setClass('Foo') // tohle by se asi dalo vynechat
			->setFactory($foo, array($bar, '%config%'));

Tak jsem se dneska dostal k tomu abych to použil a ejhle. Nelíbí se mu to $foo predavane jako prvni parametr setFactory.

Warning
implode(): Invalid arguments passed

Nette/DI/ContainerBuilder.php:453

} elseif (!Validators::isList($entity) || count($entity) !== 2) {
	throw new Nette\InvalidStateException("Expected class, method or property, " . implode('::', $entity) . " given.");

Pokud ->setFactory($foo, array($bar, '%config%')); přepíšu na ->setFactory('@container::create' . ucfirst($this->prefix('foo')), array($bar, '%config%'));. Tak to funguje jak má.

David Grudl
Nette Core | 8227
+
0
-

Patrik Votoček napsal(a):

Tak jsem se dneska dostal k tomu abych to použil a ejhle. Nelíbí se mu to $foo predavane jako prvni parametr setFactory.

Opraveno.

David Grudl
Nette Core | 8227
+
0
-

Proki napsal(a):

…Proto se chci zeptat, zda existuje nějaký novější a v budoucnu podporovaný přístup, jak se dostat k těmto parametrům. Nebo je to obecně chyba a presentery na tyto parametry šahat nemají?

Zatím neexistuje, označení deprecated zruším.

juzna.cz
Člen | 248
+
0
-

Proc je nutne vytvaret RobotLoader „rucne“ a nejde to uz delat v configu? Zvlaste kdyz bych chtel mit adresare pro loading zavisle na projektu a tudiz je mit nejlepe v configu

juzna.cz
Člen | 248
+
0
-

Davide, prosim dopis do kucharky jeste co udelat, kdyz mam vlastni tridu s hromadou createServiceX metod a chci je vsechny zaregistrovat ;)

Filip Procházka
Moderator | 4668
+
0
-

Přepsat ty createServiceX na služby pomocí configu nebo CompilerExtension :)

David Grudl
Nette Core | 8227
+
0
-

juzna.cz napsal(a):

Proc je nutne vytvaret RobotLoader „rucne“ a nejde to uz delat v configu? Zvlaste kdyz bych chtel mit adresare pro loading zavisle na projektu a tudiz je mit nejlepe v configu

Můžeš klidně RobotLoader vytvořit jako službu v configu. Jen musíš zajistit, aby při generování SystemContainer z configu byly k dispozici všechny potřebné třídy.

David Grudl
Nette Core | 8227
+
0
-

bojovyletoun napsal(a):

Mám takovou potíž s DI kontejnerem. Jde o to, že do hotového containeru se při přidání služby do property $classes kontejneru sám nepřidá záznam spojující interface=>název služby.

Je to tak. addService by mohl tabulku $classes rozšiřovat automaticky, ale jen pro hotové služby, nikoliv pro služby přidávané callbackem – to by totiž bylo velmi pomalé. Z toho důvodu to není implementované.

Rozumnější se mi jeví službu v configu definovat a pak ji jen metodou addService() přepsat.

mkoula
Backer | 57
+
0
-

Tak jsem docela rozhozen novými změnami, kdy mi vše přestalo fungovat a ať se snažím najít cokoli, tak nikde o nové vezi nic moc není. Používal jsem modelLoader a DibiConnection neb mi Nette\Database přijde poněkud omezená.

V config.neon jsem měl:

<script>
common:
    php:
        date.timezone: Europe/Prague

    #parametry pro připojení k databázi
    database:
        hostname: localhost
        username: jarda
        password: sleva
        database: mydb
        charset: utf8
        profiler: TRUE

    services:
        database:
            class: DibiConnection
            arguments: [%database%]
        modelLoader:
            class: ModelLoader
            arguments: [@container]
</script>

Můžete mi někdo říci jak toto zapsat novým způsobem? Pokaždé skončím u vyjímky: „Found sections ‚database‘, ‚models‘ in configuration, but corresponding extensions are missing.“ Každopádně robotloader je najde, v cache souboru vidět jsou…

Zkoušel jsem všechno možné, ale spíš zabředávám do větších a větších konstrukcí, které nikam nevedou :-(

Pavel Kouřil
Člen | 128
+
0
-

Parametry musí být pod „sekcí“ parameters, aby se to kompilátor nesnažil zkompilovat jako extension (viz. https://doc.nette.org/cs/configuring#…) :)

Elijen
Člen | 171
+
0
-

Patrik Votoček napsal(a):

Musíš si udělat vlastní Nette\Config\CompilerExtension a udělat si metodu Nette\Config\CompilerExtension::afterCompile.

	$class->addExtend('MySuperExtraContainer');

Zkoušel jsem postup, který popisuješ, ale vygeneruje mi to následující kód, což je syntax error:

class SystemContainer extends Nette\DI\Container, \Containers\MySuperExtraContainer

Jaký má smysl metoda addExtend, kdyz PHP nema vicenasobnou dedicnost? Neměla by místo metody addExtend spise existovat metoda setExtends?

Edit:
Tak jsem to obesel takto:

$class->extends = array('\Containers\DatabaseContainer');

Plus jeste v closure nefunguje new static, takze je treba predat instanci z vnejsku pomoci use. (to je srandy takhle po ranu :-))

Editoval Elijen (11. 2. 2012 3:05)

Filip Procházka
Moderator | 4668
+
0
-

Jelikož ClassType je obecně pro třídy i interfacy, tak to smysl má ;)

PS: tfuj :P

$configurator->addParameters(array('container' => array(
	'parent' => 'Containers\DatabaseContainer'
)));

Editoval HosipLan (11. 2. 2012 11:00)

Elijen
Člen | 171
+
0
-

HosipLan napsal(a):

Jelikož ClassType je obecně pro třídy i interfacy, tak to smysl má ;)

PS: tfuj :P

$configurator->addParameters(array('container' => array(
	'parent' => 'Containers\DatabaseContainer'
)));

Nj par minut po tom, co jsem to postnul jsem videl Daviduv prispevek:

parameters:
	container:
		parent: Containers\DatabaseContainer

a za dalsich par minut, jsem zjistil, ze to pujde udelat jeste lip bez vlastniho containeru pouze tovarnickama :-D

mlha
Člen | 58
+
0
-

Přecházím z verze 0.9 na 2.0
Můžete mi prosím polopaticky vysvětlit jak se dostanu ke konfiguračním parametrům v novém NEON formátu?
Původní zápis z configu:

variable.dblog = TRUE

Načtení v kódu

\Nette\Environment::getVariable('sso')
Vojtěch Dobeš
Gold Partner | 1316
+
0
-

V NEONu takto:

parameters:
	foo: true

Při předání nějaké službě v NEONu takto:

services:
	FooService:
		factory: \Model\FooService( %foo% )

V presenteru takto:

$this->context->parameters['foo'];`
mlha
Člen | 58
+
0
-

bezva diky a mimo presenter?
treba jeste hned v bootstrapu?

Vojtěch Dobeš
Gold Partner | 1316
+
0
-

Hned na instanci $containeru.

$container = $configurator->createContainer();
$foo = $container->parameters['foo'];
$container === $presenter->context