[2009–08–19|c2ebb91] Důležitá změna API ServiceLocator (+ konfigurace)
- David Grudl
- Nette Core | 8218
V 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();
- Honza Marek
- Člen | 1664
Jj, vypadá to super. Jen jsem si téměř jist, že místo
option
budu do configu cpát options
…
- David Grudl
- Nette Core | 8218
Honza M. napsal(a):
Jj, vypadá to super. Jen jsem si téměř jist, že místo
option
budu do configu cpátoptions
…
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
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
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átoptions
…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
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 | 8218
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.
- jasir
- Člen | 746
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 | 8218
Spíš bych nechtěl vytvářet další jazyk, kterým by šlo zapisovat PHP pomocí INI souboru.
- Panda
- Člen | 569
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 | 8218
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.
- romansklenar
- Člen | 655
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 | 8218
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
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
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
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
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).