[2011–12–12] Přechod na nový Configurator v poslední verzi
- David Grudl
- Nette Core | 8218
[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á createArticle
z dokumentace
DI, dědičnost služeb je popsána v rodící se dokumentaci konfigurátoru.
- Patrik Votoček
- Člen | 2221
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
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
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.
- Fanda
- Člen | 39
@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
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
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
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
Máš v loadConfig přístupný public
ContainerBuilder::$parameters
, který se expanduje do Containeru.
Zkusil bych to tudy.
- David Grudl
- Nette Core | 8218
API CompilerExtension není definitivní, nicméně je to poslední věc, do které plánuju v DI případně zasahovat.
- David Grudl
- Nette Core | 8218
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
Poznámka ke config.neon ze sandboxu, PostgreSQL a DiscoveryReflection:
https://forum.nette.org/…ryreflection#…
- Čelo
- Člen | 42
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í?
- Patrik Votoček
- Člen | 2221
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 | 8218
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
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)
- Patrik Votoček
- Člen | 2221
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).
- Proki
- Člen | 66
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
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
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
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 | 8218
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 | 8218
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.
- Filip Procházka
- Moderator | 4668
Přepsat ty createServiceX na služby pomocí configu nebo CompilerExtension :)
- David Grudl
- Nette Core | 8218
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 | 8218
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
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
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
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
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
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
- Vojtěch Dobeš
- Gold Partner | 1316
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'];`
- Vojtěch Dobeš
- Gold Partner | 1316
Hned na instanci $container
u.
$container = $configurator->createContainer();
$foo = $container->parameters['foo'];
$container === $presenter->context