[2009–08–19|c2ebb91] Důležitá změna API ServiceLocator (+ konfigurace)

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

aktuální revizi došlo ke zpětně nekompatibilní změně v ServiceLocator a IComponent API.


ServiceLocator patří do core vrstvy frameworku a pokud jej nepoužíváte (tj. nevoláte metody addService() nebo getService()), můžete následující dva odstavce přeskočit. Hned za ním bude následovat vysvětlení, jakou novou funkčnost změny přinášejí.

Pro ostatní ukázka starého a nového API ServiceLocator. Klíčovou změnou je přehození prvního a druhého parametru u metody addService() – stačí tedy v kódu oba parametry prohodit a mělo by vše fungovat. Dřívější pořadí parametrů bylo velmi matoucí. Pokud jako službu uvádíte callback na továrnu ve tvaru „Trida::metoda()“, odstraňte závorky (smysl závorek padl se změnou oddělovače jmenných prostorů v PHP 5.3). Dále metoda getService() nemá druhý parametr $need – pokud potřebujete zjišťovat existenci služby, využijte metodu hasService().

Druhá změna se týká IComponent (ukázka starého a nového API), kde byla odstraněna experimentální podpora pro ServiceLocator. Vlastní locator pro komponenty se nikdy neujal, praxe ukazuje, že lokátor je potřeba spíš mimo strukturu komponent. API se tak zjednodušuje a je to první krok k tomu, aby bylo možno zprovoznit serializaci komponent. Pokud jste ServiceLocator u komponent používali a změna se vás dotkla, napište, najdeme nějaké řešení.

Chytřejší ServiceLocator + config.ini

Mít chytřejší ServiceLocator (a hloupější objekty) jsem měl v plánu docela dlouho, leč na úpravu stále nějak nebyl čas. U vylepšeného lokátoru lze určit, jestli je služba singleton a podle toho při každém požadavku (getService()) ji buď znovuvytvoří, nebo vrátí vždy tentýž objekt. Dále je možné továrně předat pole uživatelských parametrů – čtvrtý parametr addService() nebo druhý parametr getService() v případě ne-singletonů.

Zajímavé to začíná být ve spojení s konfigurací přes config.ini. Původní zjednodušená syntax

[common]
service.Nette-Security-IAuthenticator = Users

může být rozšířena (tj. jsou podporovány oba typy zápisu):

[common]
service.Nette-Security-IAuthenticator.factory = Model::createAuthenticator  ; nebo lze uvést jméno třídy
service.Nette-Security-IAuthenticator.singleton = TRUE ; což je i výchozí hodnota
service.Nette-Security-IAuthenticator.option.caseSensitive = TRUE
service.Nette-Security-IAuthenticator.option.useHash = FALSE

Takto je možno v konfiguraci podrobně popsat vytvářenou službu, tj. uvést všechny parametry metody addService().

Při prvním požadavku na službu (tj. při zavolání getService('Nette\Security\IAuthenticator')) se zavolá továrna, tedy statická metoda Model::createAuthenticator($options), kde $options je asociativní pole tvořené dvěma prvky uvedenými v konfiguraci. Továrnička vrátí objekt, ten se uloží a vrátí při každém dalším požadavku na stejnou službu. Kdyby bylo nastaveno singleton = FALSE, bude továrnička volána pokaždé znovu.

Navíc je možné službu nechat nastartovat. To se může hodit v případě, že si vytvoříme továrničku například na RobotLoader nebo Session. Konfigurace a registrace RobotLoaderu se tak může z bootstrap.php klidně přesunout do config.ini:

service.RobotLoader.factory = Nette\Configurator::createRobotLoader
service.RobotLoader.option.directory[] = "%appDir%"
service.RobotLoader.option.directory[] = "%libsDir%"
service.RobotLoader.run = TRUE ; spustí službu ihned po Environment::loadConfig()

V příkladu uvedená továrnička Nette\Configurator::createRobotLoader skutečně existuje. Její implementace nevyžaduje seznam indexovaných adresářů – pokud je vynecháme, použije se výchozí hodnota, což jsou právě cesty %appDir% a %libsDir%. Tudíž oba řádky service.RobotLoader.option.directory můžeme v tomto případě vynechat. Dokonce tato továrna je už předdefinovaná pod službou Nette\Loaders\RobotLoader – tedy vlastně stačí udělat jediné – službu nastartovat:

[common]
service.Nette-Loaders-RobotLoader.run = TRUE

Tento jeden řádek v config.ini eliminuje potřebu aktivovat RobotLoader v bootstrap.php.

Konfigurační soubor tak získává na síle. Mým záměrem je sjednotit možnosti konfigurace klíčových služeb a mít tak možnost nastavovat parametry Session nebo Application přímo v config.ini. Samozřejmě je čistě na programátorovi, který přístup použije.

Chytřejší třída Environment

Služby uložené v globálním service locatoru ve třídě Environment lze získat přes:

$service = Environment::getService('Nette\Web\IHttpRequest');

// což lze nahradit pohodlnějším a kratším
$service = Environment::getHttpRequest();

Nyní je možné v PHP 5.3 podobné aliasy uživatelsky definovat:

Environment::setServiceAlias('Nette\Web\IHttpRequest', 'xyz');
$service = Environment::getXyz();
jasir
Člen | 746
+
0
-

Hmmm, tak to vypadá vážně naprosto luxusně… Díky, jdu to zkoumat…

Honza Marek
Člen | 1664
+
0
-

Jj, vypadá to super. Jen jsem si téměř jist, že místo option budu do configu cpát options

David Grudl
Nette Core | 8227
+
0
-

Honza M. napsal(a):

Jj, vypadá to super. Jen jsem si téměř jist, že místo option budu do configu cpát options

Mám stejný problém ;) Ale když už se v config.ini používají pouze jednotná čísla, chce se toho držet.

jasir
Člen | 746
+
0
-

Nyní se do factory metody / konstruktoru objektu dá jako argument poslat jen associativní pole options. Nestálo by za zvážení zda neimplementovat i podporu pro posílání parametrů do konstrukturu, tj. aby při hypotetickém

service.MyServices-Mailer = MyMailer
service.MyServices-Mailer.option.smtp = ...
service.MyServices-Mailer.option.password = ...
service.MyServices-Mailer.option.login =...
service.MyServices-Mailer.optionsAssoc = FALSE ;nepředávat přes options
service.MyServices-Mailer.singleton = FALSE;

se objekt service vytvořil pomocí volání:

<?php
new MyMailer($smtp, $password, $login)`
?>

Pak by se snadno dali dělat z jakýchkoliv tříd service bez vytváření factoryMetody.

A také dala by se poslat jako option „další service“, tedy například:

service.MyServices-BulkMailer = BulkMailer
service.MySevices-option.mailer = service.MyServices-Mailer

Při vytažení service BulkMailer by se nejprve vytvořil objekt MyMailer a ten by systém poslal jako parametr do konstruktoru BulkMaileru.

Editoval jasir (19. 8. 2009 19:20)

Honza Marek
Člen | 1664
+
0
-

David Grudl napsal(a):

Honza M. napsal(a):

Jj, vypadá to super. Jen jsem si téměř jist, že místo option budu do configu cpát options

Mám stejný problém ;) Ale když už se v config.ini používají pouze jednotná čísla, chce se toho držet.

Ono to množné má tady svojí logiku. Konkrétní volba je přece prvkem „objektu“ volby a ne „objektu“ volba.

kravčo
Člen | 721
+
0
-

Honza M. napsal(a):

Ono to množné má tady svojí logiku. Konkrétní volba je přece prvkem „objektu“ volby a ne „objektu“ volba.

No, vidím, že i logika môže byť relatívna. Mne oveľa logickejšie pripadá terajší stav, v jednom riadku predsa nastavujem len jednu voľbu…

; nastav voľbu „authorize“ službe „User“
service.User.option.authorize = TRUE

Editoval kravčo (19. 8. 2009 19:49)

David Grudl
Nette Core | 8227
+
0
-

jasir napsal(a):

Nyní se do factory metody / konstruktoru objektu dá jako argument poslat jen associativní pole options. Nestálo by za zvážení zda neimplementovat i podporu pro posílání parametrů do konstrukturu, tj. aby při …

Pokud je uvedena třída, pokusí se zavolat metodu setOptions(), existuje-li. Zatím jde o experimentální implementaci.

A také dala by se poslat jako option „další service“, tedy například:

To už překračuje hranice jednoduchosti. Jak je potřeba logika, použij továrničku.

Honza Kuchař
Člen | 1662
+
0
-

Vypadá opravdu naprosto luxusně! :)

jasir
Člen | 746
+
0
-

David Grudl napsal(a):

A také dala by se poslat jako option „další service“, tedy například:

To už překračuje hranice jednoduchosti. Jak je potřeba logika, použij továrničku.

Okej, díky za odpověd. Inspirací mi byl dependency injection ze Symphony, ale rozumím, touto cestou se Nette zatím nebude vydávat.

Editoval jasir (19. 8. 2009 21:15)

David Grudl
Nette Core | 8227
+
0
-

Spíš bych nechtěl vytvářet další jazyk, kterým by šlo zapisovat PHP pomocí INI souboru.

Panda
Člen | 569
+
0
-

David Grudl napsal(a):

service.RobotLoader.factory = Nette\Configurator::createRobotLoader
service.RobotLoader.option.directory[] = "%appDir%"
service.RobotLoader.option.directory[] = "%libsDir%"
service.RobotLoader.run = TRUE ; spustí službu ihned po Environment::loadConfig()

Neměl by zde být uveden i namespace? Tzn.

service.Nette-Loaders-RobotLoader.factory = Nette\Configurator::createRobotLoader
service.Nette-Loaders-RobotLoader.option.directory[] = "%appDir%"
service.Nette-Loaders-RobotLoader.option.directory[] = "%libsDir%"
service.Nette-Loaders-RobotLoader.run = TRUE ; spustí službu ihned po Environment::loadConfig()

Když jsem zkusil použít původní verzi, tak mi aplikace při pokusu o automatické spuštění služeb skončila výjimkou Service 'RobotLoader' not found..

David Grudl
Nette Core | 8227
+
0
-

Název služby je zcela libovolný řetězec. Když ji nazvu RobotLoader, tak ji získám přes Environment::getService('RobotLoader'), případně pomocí aliasu jakkoliv.

LM
Člen | 206
+
0
-

V Application::setRouter() se volá addService() ještě s neprohozenými parametry.

David Grudl
Nette Core | 8227
+
0
-

opravim

Inza
Člen | 330
+
0
-

Oh man! Tak tomu říkám upgrade!:-) Jdu přepsat config a bootstrap! Skvělá práce!:-)…

P.S.: A Davide…už to ti říkal myslím i Vítek – my dva si musíme promluvit o NDP – zcela vážně. ASAP. Mohl bych tě zastihnout někdy online na nějakém jabberu?

romansklenar
Člen | 655
+
0
-

config.ini

; variables and constants
variable.testsDir = "%appDir%/../tests"


; services
service.Nette-Loaders-RobotLoader.option.directory[] = %appDir%
service.Nette-Loaders-RobotLoader.option.directory[] = %libsDir%
service.Nette-Loaders-RobotLoader.option.directory[] = %testsDir%
service.Nette-Loaders-RobotLoader.run = TRUE

bootstrap.php

$loader = Environment::getService('Nette\Loaders\RobotLoader');
Debug::dump($loader);

object(RobotLoader) (9) {
   "scanDirs" => array(3) {
      0 => string(25) "D:\web\www\nette-app\app"
      1 => string(26) "D:\web\www\nette-app\libs"
   }
   ....
}

Je to chtěné chování? Čekal jsem, že testsDir bude taky v poli RobotLoader::$scanDirs (adresář na FS existuje).


Když už jsem u toho loaderu, nešlo by rozšířit možnosti konfigurace, aby šlo dosáhnout třeba tohoto?

[common]
service.Nette-Loaders-RobotLoader.option.autoRebuild = TRUE

[production < common]
service.Nette-Loaders-RobotLoader.option.autoRebuild = FALSE
David Grudl
Nette Core | 8227
+
0
-

Tam musí být uveden ještě řádek

service.Nette-Loaders-RobotLoader.factory = Nette\Configurator::createRobotLoader

Chybou je, že ve všech config.ini v distribuci chybí. Jen teď váhám, jestli ho tam doplnit, nebo naučit konfigurátor fungovat bez něj. Co je méně WTF…

BigCharlie
Člen | 283
+
0
-

Je to chtěné chování? Čekal jsem, že testsDir bude taky v poli RobotLoader::$scanDirs (adresář na FS existuje).

Tohle už fórem prošlo

Co je méně WTF

Asi jak pro koho. Pro nováčka bude lepší vidět v distribuci řádek navíc. Zkušenější asi taky nebudou nic namítat?

Tomik
Nette Evangelist | 485
+
0
-

David Grudl napsal(a):

Jen teď váhám, jestli ho tam doplnit, nebo naučit konfigurátor fungovat bez něj. Co je méně WTF…

Co oboje? Tedy řádek uvést, aby bylo jasné „co to dělá“, ale naučit mechanizmus fungovat i bez něj, aby to bylo „zpětně kompatibilní“ (schválně v uvozovkách).

Editoval Tomik (26. 11. 2009 23:54)

Honza Kuchař
Člen | 1662
+
0
-

Jsem taky pro oboje. Dneska jsem na to zarazil při vytváření nového příkladu k FileDownloaderu. :) Ještě, že jsem to tu už četl.

arron
Člen | 464
+
0
-

Osobne bych se priklanel k tomu, ze pokud uz jsou v ServiceLocatoru nejake servisy defaultne prednastavene, tak v config.ini muzu menit jejich parametry, ale ty, ktere neprepisu tam zustanou. Takze bych mohl nadefinovat napr. jine adresare pro RobotLoader (cimz by se samozrejme prepsaly defaultni hodnoty), ale uz bych v tom pripade nemusel pridavat znovu definici factory (pokud bych ji ovsem nechtel zmenit).