Dnes je ještě jednou vrátím k tématu DI a vysvětlím proč Jet není to čemu se říká DI framework i když by bez základního DI princip nefungoval.
Určitě se bez debat shodneme na tom, že psát aplikace třeba takto:
class SomeController extends SomeAbstractController { public function someAction() : void { //... $logger = new Logger(); $logger->log( ‘událost’, $data ); //... } }
… je zcela špatně. To beru jako naprosto samozřejmou věc.
Logger (v tomto kontextu v roli služby jiné třídy) nemá být instancován a inicializován ono třídou která jej používá – klientem / konzumentem služby.
Pro pořádek proč je to objektivně špatně:
Prostě a jednoduše: Takto (jak je uvedeno v příkladu) se neprogramuje už z principu a programátor takto ani nemůže uvažovat o návrhu aplikace. A v Jetu se tak rozhodně neuvažuje. To beru jako naprostou samozřejmost a něco naprosto přirozeného o čem není třeba diskutovat.
Stejně tak není možné používat statické třídy tohoto charakteru:
class Auth { public static getCurrentUser() : ?array { $current_user_id = static::getCurrentUserID(); if($current_user_id) { $user = Db::read(“SELECT …..”); //.... } //.... } }
Rozhodně vyloučeno! To je naprosto neflexibilní kód, pro který ani nemusíme mít OOP, ale stačily by pouhé funkce. To je k ničemu … To nemá s OOP vlastně nic moc společného.
I když pozor! Toto se dá později refaktorovat na to co budu popisovat v tomto článku.
Koncept vkládání závislostí nabízí možnost, jak se tomu všemu vyvarovat. Přináší různé abstraktní pojmy (rozhraní, služby, kontejnery, injektory, …) ke kterému já si dnes dovolím přidat další. Pojem, kterému jsem se rozhodl říkat garant služby. Toto označení by mělo lépe vystihovat podstatu věci.
Prostě konečně vysvětlím proč to mám udělané tak a tak, proč to používá DI, ale zároveň nepoužívá předávání služeb formou parametrů konstruktoru. A proč toto řešení považuji za velice dobré řešení pro daný typ aplikací.
Pamatuji si, jak jsem před desítkami let četl knihu vysvětlující (obecné) základy OOP a tam se jako příklad používala zvířata. Kočka, pes … Já si tuto metodu výkladu s dovolením vypůjčím.
Představte si, že ráno vstanete a začíná nový den, během kterého budete dělat spoustu věcí. Dejme tomu, že ráno vzniká naše nová instance naší třídy, která bude bude během dne (životního cyklu – teď se zdržme filozofování a prostě to tak vezmeme jako příklad) potřebovat řadu služeb.
Tak co má člověk v plánu? Dejme tomu, že je to volný den a našinec si to udělá fajn. Ráno zajede do fitka, kde bude potřebovat službu trenér. Pak zajde k kadeřnici, kde jej jeho oblíbená paní ostříhá. Pak určitě bude potřebovat služby číšníka v restauraci, kam si zajde na oběd. A tak dále. Prostě člověk si žije svůj život a používá různé služby.
Tak to chodí, ne?
A teď si představte ten bizár. Ráno mžouráte oči jen co se probouzíte – když se volá váš konstruktor. A u vaší postele už stojí trenér, kadeřnice i číšník. Někdo vám je natlačil až do ložnice s tím, že je budete (možná! to je velice důležité: možná!) potřebovat. Já nevím jak vy, ale já bych hodně zíral …
Když pominu, že toto je samo o sobě dosti bizarní, tak to ani neodpovídá tomu jak se věci na světě dějí.
Taková instance třídy Člověk je dosti komplexní věc řešící mnoho záležitostí (samozřejmě v neustále kooperaci s někým a něčím). A může se krásně stát, že cestou do fitka se instance člověka nečekaně setká s jinou instancí, třeba instancí třídy Lupič a dojde k vyhození výjimky KrádežVTramvaji. Ano, je to extrém, ale stát se může. Pak jsou člověku trenér, kadeřnice i číšník co se s nimi od rána vláčí už z ložnice přes záchod a koupelnu až na zastávku MHD (pokud by mu je někdo fakt natlačil-injektoval do ložnice) úplně k ničemu. Tyto služby již nebude potřebovat a naopak bude potřebovat služby Policie, ÚřaduMeštskéČásti, Banky a bůh ví co ještě.
Už víte kam směřuji?
Jde o to, že třídy požadující a využívající nějaké služby nejen že nesmí řešit vytváření a inicializaci poskytovatelů těchto služeb, ale dokonce ani není nutné aby držely jejich samostatné instance. A dokonce to v některých (ne všech – k tomu se dostanu) případech není vůbec vhodné.
V reálném světě je to totiž takto:
My se nestaráme o to, jak se kdo stane trenérem, či kadeřnicí. To je nám jedno. A v drtivé většině případů si nesháníme naší osobní kadeřnici, či osobního trenéra ( i když můžeme, ale není to standard ). V reálném životě víme, že jsou věci a instituce, které nám garantují poskytnutí dané služby. A teď nemyslím to, že nám pošlou trenéra a kadeřnici ráno domů. To vůbec nepotřebujeme! My prostě potřebujeme, aby nás někdo chvíli trénoval, aby nás kadeřnice ostříhala, aby náš povoz měl energii na další ježdění, aby nám někdo dovezl jídlo tam kde si jej koupíme a tak dále. V reálném světě potřebujeme aby nám někdo garantoval a zajistil poskytnutí daných služeb a už je v jeho kompetenci jak to udělá a vyřeší si své závislosti a záležitosti – třeba kde a jak sežene a zaplatí majitel kadeřnictví kvalifikovanou kadeřnici.
Mimochodem, malá ale důležitá odbočka … Dost důležitá OOP-filozofická otázka: Je nějaká instance kadeřnice, či trenéra vlastností třídy Člověk? Zdálo by se že je, ale ve skutečnosti ne. Jsou to pouze služby které člověk potřebuje, ne vlastnosti. Vlastnosti jsou výška, váha, datum narození, … A dá se říct, že i instance mého syna je má integrální vlastnost (té se nevzdám a zároveň pevně ovlivňuje můj život). Ale ne instance mé kadeřnice. Protože pokud by kadeřnice byla mou vlastností, tak je vlastně mou vlastností celý svět. I letadlo kterým mohu letět na dovolenou! A to by byl fakt zmatek … Vše by se totálně zamotalo a vše by bylo vlastností všech. Prosté službu prostě nejsou přirozené vlastnosti tříd – uživatelů / konzumentů služeb, ale pouze a jenom poskytovatelé služeb, kteří nemají s klientem nějakou užší a dočasnou vazbu, lépe řečeno interakci. Nic míň, nic víc.
A pozor! Aby to nebylo pochopeno tak, že žádná služba (její instance) nemá být vlastností třídy. Jako se vším a ve všem záleží na konkrétní situaci.
Pokud by měla mít služba nějaký vnitřní stav relativní ke klientské třídě a mělo by být zaručeno že služba bude i nadále poskytována stejnou službou se stejným vnitřním stavem, tak je rozhodně lepší, aby instance poskytovatele služby byla vlastností klienta / konzumenta služby.
Přirovnejme to opět ke kadeřnici. Pokud by se mělo běžně stávat, že během stříhání šéf té kadeřnice ji odvolá a pošle nám úplně novou, takovou paní která nezná náš požadavek a bude znova nutné ji vysvětlit jak dokončit již započatý střih, tak v takovém případě je rozhodně lepší si danou kadeřnici alespoň po dobu provádění služby držet jako svou instanci – svou vlastnost. Ale hlavní otázka je: Stává se to? Je to reálné? Co je běžná situace a předpokládané chování a co je již nepravděpodobný extrém?
V případě kadeřnice se toto běžně nestává a vlastně stát nesmí. Je v kompetenci garanta služby a je povinností garanta služby nic takového nedopustit, protože by to byla vada poskytované služby.
Ale co třeba instance nějakého spojení někam v rámci třídy? Může jít o spojení na databázi, či již předkonfigurovaný klient nějakého API a tak dále. To je věc, která již má nějaký vnitřní stav (například parametry spojení) a ten je relevantní vůči klientské třídě a navíc by reálně hrozilo že něco zvenčí může tento stav změnit. A ano, v takovém případě je dobré si danou službu držet jako sanostatnou instanci. Otázka je, zda to považovat ještě za službu a jak to nazvat. Je to ještě služba? Ale to už bych moc odbočil a pustil bych se do filozofování. Ale ano, takové situace se dějí a v takových situacích jsou globální stavy nepřípustné a instanci takové věci je nutné držet v kontextu té instance vůči které je to v kontextu. Prostě a jednoduše řečeno: Například instanci spojení na databázi by si měla určitá třída držet u sebe a nemá to být globální. A není to situace, kterou teď řešíme.
Vždy je nutné posoudit, zda to a to je opravdu daná situace a jaké řešení je vhodné použít. Určitá poučka (např. ta o špatnosti globálních stavů) může být pro určitou situaci zcela správná, ale pro určitou situaci naprosto chybná, protože naopak záměrně potřebujeme pravý opak. To si hned ukážeme.
Služby jako kadeřnici, restaurace, fitka a jakékoliv běžné věci mohu měnit jak libo podle mých preferencí. Ostatně se to tak nějak i z principu očekává. Garanty poskytnutí služeb měníme v životě zcela běžně.
Ale jsou situace, kdy nám danou službu už z principu musí garantovat jen a pouze jedna instituce. Už jsem narazil na to, že náš pomyslný člověk byl bohužel ráno v tramvaji okraden. Bude tedy potřebovat velice choulostivé služby a k tomu jejich garanty. Bude potřebovat Policii ČR (v naději že s tím něco udělá, nebo alespoň dá papír) a státní aparát.
A tu Policii, která reálně šetří kriminalitu máme a musíme mít jen a pouze jednu – musí být globální. Městské a obecní policie jsou něco jiného a moc dobře vědí, kde končí jejich kompetence – je to jiná služba, pro daný moment irelevantní. Státní policie, která má podobné nepříjemnosti v kompetenci je jen a pouze jedna – je to Policie ČR.
A kdybychom měli třeba 2, 3, 4 státní policie snažící se garantovat poskytnutí téže služby, tak by to bylo k ničemu. Byl by to zmatek a celý systém by nefungoval. Garanti služeb by se mezi sebou ve finále hádali a vše by kolidovalo a kolabovalo. Jsou tedy situace, kdy je žádoucí, kdy je naprosto nutné, aby určitou službu garantovala jen a pouze jedna instituce. A pochopitelně i tato instituce má opět své závislosti, své vnější rozhraní (linka 158 a operační oddělení), vnitřní rozhraní, své továrny, své vnitřní subslužby (náboráře, policejní akademii, dodavatele vybavení …) Ale pro nás běžné smrtelníky je to jeden jediný garant služby a víme že je jeden a chceme aby byl jeden.
Teď se vraťme k začátku dne do ložnice onoho člověka co má tak zběsilý den, že potřebuje Policii ČR. Dokážete si představit, že někdo by byl tak prozíravý a kromě instance konkrétní kadeřnice, konkrétního trenéra a konkrétního číšníka by mu do ložnice k posteli poslal rovnou i třeba policejní hlídku? Bizár, že? Jenže řada lidí tvrdí, že takto se má programovat a takto řešit závislosti všeho na všem. Kdepak … Jde to i jinak.
A teď k našim programům. Není náhodou velká část služeb jako je autentizační a autorizační subsystém, logování, služby mající charakter jádra systému (hlavní router a jeho stav – MVC) a tak dále, nejsou tyto důležité věci už z principu globální? Nejsou to výhradní garanti služeb? Jsou. V těchto určitých situací potřebujeme výhradní garanty služeb. Zde to není špatně, ale naopak – je to žádoucí.
To si ujasněme hned na začátku než se hlouběji pustím do pojmu garant služeb. To čemu říkám garant služby není service locator. Možná by se pro to hodil termín fasáda (tak jsem to označoval a dělá to tak i Laravel, k čemuž se ještě dostanu), ale ani to vlastně není úplně přesně a pletlo by se to s tím co dělá právě Laravel – jak jsem si postupně uvědomil a až ex-post mi došlo, že takto to nemusí být zřejmé.
A zároveň se teď vůbec nebavíme o věcech jako je např spojení na jiný systém. Tedy o věcech, které mají existovat v samostatných instancích vázaných na instanci se kterou jsou pro daný účel v kontextu. Někdy je lze chápat spíše jako entity (má to onen vnitřní stav, pro kontext relevantní) a pro jejich vytváření a inicializaci je nejvhodnější použít továrny.
Garant služby je něco o čem vím, že má nějaké jasně dané komunikační rozhraní a princip fungování a vím že mi to poskytne určitou globální službu.
Ale pozor! Garant služby se neřídí jen tím co chce a jak chce on. Dejme stranou kadeřnictví a koukněme se na Policii ČR – to je pro teď mnohem lepší příklad.
O Policii ČR víme, že má poměrně jasně dané role a bude vykonávat poměrně jasně dané služby nám všem. Ale jak přesně to bude dělat? Stanoví si sama pravidla? Nechme stranou, že konkrétní hlídku pošle operační důstojník – takové detaily teď neřešme a koukejme na to z nadhledu.
V normálním fungujícím státě rozhodně způsob práce policie určuje vyšší autorita a policie se řídí zákony, které musí dodržovat. Policie ČR funguje na základě zákonů a pravidel, které jí stanovila zákonodárná moc republiky jakožto vyšší autorita a tato pravidla do Policie vlastně injektovala. Policie (nebo třeba i Armáda ČR – také dobrý příklad) se těmito pravidly řídí a nevymýšlí si vlastní legislativní rámec – vlastní řídící logiku. Ale ostatně i kadeřnický salón musí plnit nějaká pravidla vložená zvenčí, restaurace taktéž. Taktéž i to zda je někdo přijat třeba k té Policii ČR a může se tedy stát poskytovatelem služeb je dáno legislativním rámcem – pravidly vloženými zvenčí vyšší autoritou.
Garant služeb tedy ve své podstatě neobsahuje tuto základní rozhodovací logiku. Pouze dodržuje to, co je mu dáno vyšší autoritou (a má logiku, která prokazatelně spadá do pouze do jeho kompetence).
Ale service locator funguje takto:
A implementován by byl třeba nějak takto:
class ServiceLocator { public static function getLogger() : Logger_Interface { if( $neco=’A’ ) { return new LoggerA(); } if( $neco=’B’ ) { return new LoggerA(); } return new LoggerDefault(); } }
Tudy cesta nevede. Dělá si to co chce, nepodléhá to žádné kontrole zvenčí … Je to de facto to samé, jako ten příklad, kterým jsem začal na samotném začátku článku, jen jsou tam if() či jiné rozhodování.
Service locator je policie v nějaké totalitní zemi, která se kontroluje zcela sama a nepodléhá rozumné a volené vyšší autoritě a nedá se do ní nic injktovat. A jaké to je? Nic moc … Tu šeď a povinné básničky o Leninovi si já ještě pamatuji …
Lokátor služeb je opravdu špatná cesta. Ale to není příklad garanta služeb.
V aplikaci je běžné, že pro nějaký účel nutně potřebujeme výhradního garanta služeb. Jako příklad z reálného světa jsem použil Policii ČR. Jako příklad ze světa online aplikací použiji autentizační a autorizační subsystém, který jsem již nakousl.
To prostě musí být výhradní garant daných služeb a musí to mít globální stav. Je to tak trochu taková policie.
Představte si následující situaci v nějaké špatně navržené aplikaci:
Nějaký autentikátor v oné nepovedené aplikaci existuje vždy jako samostatná instance (ne singleton) předávaná vždy jako závislost uživatelům – klientským třídám, například kontrolerům. Ten kdo to navrhoval si někde asi přečetl že singleton či globální věci je fujky-fujky a že vše má předávat jako závislost do konstruktorů a tak nějak si neuvědomil, že to zrovna zde nedává smysl (opakuji: někde poučky platí, někde ne – jde o celkový kontext).
A teď se stane toto:
Co z toho plyne? V některých situacích (opakuji: v některých) je naprosto žádoucí a nutné, aby nějaký stav aplikaci byl globálně sdílený a byl garantován jednou jedinou autoritou. Úplně stejně jako potřebujeme onu jednu Policii ČR.
Tedy pro danou situaci potřebuji jednoho výhradního garanta služeb. Autentizační a autorizační mechanismus je krásný příklad – nikoliv však jediný.
Jako instanci ve formě globální proměnné? Vůbec ne! To nikoho nemůže ani napadnout, protože s tím si každá komponenta aplikace může dělat co chce. Absolutně to není garant služeb. Cokoliv to může rozbít a kdo má vědět kde je jaká zatracená globální proměnná?
Co takhle mít nějakou instanci třídy Application a garanty služeb mít jako její vlastnosti?
class Application { protected ?AuthService $auth_service = null; public function setAuthService( AuthService_Interface $service ) : void { $this->auth_service = $service; } public function getAuthService() : AuthService_Interface { //Pokud poskytovatel nebude nastaven, jde to prirozene "k zemi" return $this->auth_service; } }
To by už asi šlo. No jo, ale ta samotná instance třídy Application bude kde a jako co? A jsme opět na začátku … Opět mám nějakou globální autoritu, která nesmí viset ve vzduchoprázdnu.
Dobře, mohlo by to být statické, to by šlo, ale …
Pak je tu ještě problém. V systému máme neznámý počest služeb / přesněji neznámý počet garantů služeb. Ovšem kdybych měl nějakou třídu Application, tak ta má omezený počet vlastností. Použít dynamické vlastnosti nepřipadá v úvahu (nepřátelské pro IDE, nepřátelské pro automatickou analýzu kódu). Nabízí se mít služby jako nějaké asociované pole a používat to třeba nějak takhle:
$app->service(‘auth’)->getCurrentUser();
Ale to je taky špatně. Opět nepřátelské pro IDE, tedy i pro vývojáře, nepřátelské pro automatickou analýzu kódu a přehlednost …
Já pro to mám jednoduchou frázi: Nejde to v PHP Stormu “prokliknout”? Nenašeptává Storm tak jak má? Tak to fakt není optimální…
Zpět k úvaze. No pak už by mi jako řešení připadalo asi dobré ty služby fakt předávat konstruktorům. A vlastně bych se postupně dostal k tomu podobnému řešení jako u ostatních FW.
Ale nešlo by to jinak?
Malá rekapitulace:
Fajn, to by šlo takhle:
Princip je ten, že konzument služby již není závislý na instanci poskytovatele služby, ale ví že v systému je pevně daný bod na který se může spolehnout – garant služby.
Ovšem garant služby již na poskytovateli služby závislý je. A potřebuje aby do něj byl poskytovatel služby vložen – injektován.
Garant služby neobsahuje žádnou vnitřní logiku, která by rozhodovala o tom jaký poskytovatel služby má být použit. Garant služby očekává, že je mu jeho závislost vložena prostřednictvím jeho rozhraní.
Co bude instancí samotného poskytovatele služeb a jak bude poskytovatel služby inicializován už rozhodne nějaký injektor na základě okolností (například inicializátory Jet MVC), nebo prostě a jednoduše při inicializaci aplikace (Jet MVC není mandatorní – lze projekt vyvíjet i bez něj a inicializovat jinak) a to třeba i na základě konfigurace (například backend ORM).
Garant služby tedy může být primitivní třída mající statickou vlastnost (instance poskytovatele služby) a statické metody jako fasádu reflektující rozhraní poskytovatele služby, ale absolutně žádnou další rozhodovací logiku.
Máme cosi globálního a jasně viditelného a všeobecně známého, mohu (nebo i musím) do toho injektovat libovolné poskytovatele služeb, který však musí samozřejmě implementovat dané rozhraní.
Je to vlastně neskutečně primitivní. Ale plně funkční.
Je to opravdu tak? Splňuje to požadavky?
Ověřme si myšlenku:
Celou dobu jsem zde zmiňoval Polici a autentizační a autorizační subsystém, což je takový vzdálený ekvivalent policie v rámci aplikace. Toto je jeho garant služby:
namespace Jet; class Auth extends BaseObject { protected static Auth_Controller_Interface $controller; public static function setController( Auth_Controller_Interface $controller ): void { static::$controller = $controller; } public static function getController(): Auth_Controller_Interface { return static::$controller; } public static function checkCurrentUser(): bool { return static::getController()->checkCurrentUser(); } public static function login( string $username, string $password ): bool { return static::getController()->login( $username, $password ); } public static function logout(): void { static::getController()->logout(); } public static function getCurrentUser(): Auth_User_Interface|bool { return static::getController()->getCurrentUser(); } public static function getCurrentUserHasPrivilege( string $privilege, mixed $value=null ): bool { return static::getController()->getCurrentUserHasPrivilege( $privilege, $value ); } public static function handleLogin(): void { static::getController()->handleLogin(); } }
Naprosto primitivní, ale účinné.
Napadá vás otázka: jak to rozšířit? Pamatuji si, že nad tím jsem se taky zasekl když jsem zkoušel tuto cestu. Ovšem v praxi je to tak, že onu elementární logiku rozšiřovat vlastně nepotřebuji. Potřebuji jednak definovat chování (což mi zajistí onen auth kontroler, jakožto injektovaný poskytovatel služby) a hlavně v praxi potřebuji rozšiřovat hlavně entity. Zde konkrétně uživatele, jehož minimální garantované rozhraní je dáno Auth_User_Interface, ale jinak je to samostatná entita v aplikačním prostoru kterou mohu mít implementovanou jak chci – ve frameworku je jen a pouze ten interface. Od autorizace chci pouze aby mi vrátila přihlášeného uživatele a jakou třídou bude uživatel konkrétně reprezentován už je věcí poskytovatele oné služby – zde toho čemu říkám Auth Controller. Jak bude uživatel implementován? Kde a jak bude uložen? Kolik o něm budu zaznamenávat informací? To je vše věcí aplikace.
No a hlavně … Když má být něco garantované rozhraní, tak to musí být garantované rozhraní. Tedy rozhraní které je pevně dané. Ale není problém vyvořit jiného garanta jiné služby – třeba i podobné a odvozené (dědičnost, přetěžování). Ale garant je prostě garant a aby nebyl zmatek, tak to musí být garant.
Je to hodně podobné, ale není to to samé. I když to má stejné benefity – srozumitelnost a snadnost použití.
Ale Laravel funguje trochu jinak.
Tam jsou to spíš proxy odkazující se na instance služeb v onom hlavním kontejneru, který Laravel má.
V Jetu tento mega kontejner vůbec není. V Jetu je každý garant služby zároveň k tomu uzpůsobeným kontejnerem pro poskytovatele dané služby, přímo pro danou službu stavěný.
Laravel používá pro fasády magickou metodu __callStatic() a funguje takto:
Navíc má Laravel krom toho i pomocné funkce. A rovněž možnost injektovat instance poskytovatelů služeb třeba do kontrolerů (a nejen tam – samozřejmě).
Nepovažuji za úplně šťastné mít X způsobů jak v jednom systému dělat stejnou věc. To může na některých projektech způsobovat nedorozumění a tedy komplikace. I proto má PHP Jet jeden jasně daný princip.
PHP Jet se zcela striktně vyhýbá používání podobných kouzel pro volání metod jako je například __callStatic. V Jetu platí, že co se má volat a používat tak má taky fakticky existovat (vlastnosti i metody musí vždy existovat). Žádná kouzla, ale transparentnost. Jak stále dokola říkám: IDE a nástroje pro analýzu kódu všemu musí ihned rozumět (bez pluginů, extra podpory, nastavení a tak dále). Jen existující kód je fakt přehledný kód.
Jet také nemá jeden mega kontejner na vše, ale každý garant služby je de facto právě pro danou službu zároveň i kontejnerem pro danou službu přímo uzpůsobený. Proto garanty služeb – zdánlivé fasády (sám jsem tak tomu říkal) nejsou zas tak úplně proxy (i když dalo by se o tom dlouze filozofovat). Hlavní je však ten rozdíl, že neexistuje žádný univerzální mega kontejner, ale garant služby je sám o sobě kontejner, který předpokládá, že službu (poskytovatele) dostane od vyšší autority injektovanou.
Laravel je fakt “jen” fasáda, či proxy – jak chcete. Ale opírá se to o služby nacházející se jinde. A používá to “magii”, zatímco v PHP Jet jsou to normální třídy.
V minulém článku o MVC jsem říkal, že není dobré mít třeba definici rout na jedné hromadě.
A úplně to samé platí pro služby a závislosti. Prostě z mnoha důvodů nemám rád házení všeho na jednu hromadu. U MVC to bylo nejmarkantnější, tak jsem o tom napsal samostatný článek.
Ale týká se to všeho. A nebylo by šťastné se těm pomyslným hromadám v jedné části systému vyhýbat a hned vedle je tvořit.
Dovolte mi ještě malou krátkou odbočku do fyzického světa a další přirovnání. Když budete vyvíjet třeba auto, tak jiné principy a jiné požadavky budete mít pro vývoj motoru, jiné na převodovku, jiné na podvozek, jiné na karoserii, jiné na elektroniku. Prostě je to řada věcí, které jsou jiné, často diametrálně odlišné a vyžadující více či méně odlišnou odbornost, ale přesto tvoří jeden celek a vše je spolu propojeno a provázáno. Ale není možné brát poučky a principy z návrhu motorů do oblasti vývoje uživatelského rozhraní používaného řidičem. Mnoho vývojářů si myslí, že určitá dobrá / dobře míněná poučka (“to je dobré”, “to je špatné”) platí globálně. Ne, není tomu tak. Svět je tvořen velice rozmanitými jednotkami. Ale ty pak tvoří jeden celky. Stejně jako třeba to auto. Nebo stejně jako software – včetně našich webových aplikací. Jen (možná by se dalo říct bohužel) ve světě SW to není tak hmatatelné, vše je tak nějak nehmotné, abstraktní. Ale je to tak.
A ty zmíněne poučky? Nelze říkat „tohle je špatně“, ale je nutné říkat „v té a té dané situaci je to špatně, protože …“, nebo „v té a té situaci je to vhodné, protože …“. Vše má svůj kontext a důvody. Ale teď zpět k tématu.
No a jak jsem již zde zmínil, tak různé garanty služeb se chovají trošku odlišně a je to zcela legitimní. Něco má definovaného výchozího poskytovatele služby, protože se v běžné situaci neočekává nahrazování onoho poskytovatele (ale zároveň jeho nahrazení musí být prostě možné), něco naopak přímo očekává a požaduje injektování poskytovatele služby do garanta. Prostě jablka nejsou jahody i když oboje může být sladké.
Strkat vše do jednoho pytle a házet vše na jednu hromadu není dobré – ani v životě, ani v SW.
Už se mi to podařilo vysvětlit? Pokud ne, tak tohle bude možná ještě lepší vysvětlení :-)
Jak jsem již v mnoha článcích vysvětloval, tak PHP Jet je pojatý vlastně jako soustava mikroaplikací, které PHP Jet poskytuje základní rámec (patrné zejména při použití Jet MVC a aplikačních modulů, ale nejen tam).
PHP Jet v plném rozsahu je vlastně tak trochu takový malinkatý operační systém, který má mikro-aplikace. (Opravdu jen trochu – neberte to doslova! Ale výstižné to je.)
A každý operační systém má nějaké služby, k těm službám se přistupuje přes API či systémová volání. Bez ohledu na to jaký to operační systém je, tak poskytovat služby a zajišťovat transparentně nějaké komunikace (SW ↔ HW, SW ↔ SW, …) je účelem OS.
Každá aplikace ví jak volat potřebné služby daného OS a když je třeba, tak je prostě používá.
OS a jeho API či systémová volání jsou v tomto kontextu vlastně velice často garantem služeb.
A takové ovladače HW a subsystémi pro práci s HW? Co vás napadá? Co to vlastně je? A co třeba subsystém pro takovou běžnou věc jako je zvuk? Co to asi tak jen může být? mrk-mrk! Nebo co síťový subsystém? Však já si na úrovni aplikace také prostě otevřu TCP port někam a nikdo mi nenutí instanci ovladače síťové karty ač teda ve finále to vše co dělám se dostane (po průchodu vrstvami systému) až k tomuto ovladači.
No a teď zpět do webové aplikace.
Co například autentizace a autorizace? To je obecná služba systému a v tomto případě musí existovat v jediné instanci a mít globální stav.
Co třeba logování? To samé. Obecná služba systému, ne služba konkrétního modulu / mikro-aplikace. Modul (nebo jakákoliv komponenta) má prostě logovat (tedy službu používat), kam “to poletí” už je věcí celého systému a předurčeno nadřazenou autoritou, která ví jak se s těmito logovanými informacemi bude zacházet.
A tak dále …
Ovšem v případě PHP Jet má aplikace (ne aplikační modul, ale projekt jako celek) možnost (a v některých případech povinnost) pomocí vkládání závislostí přes rozhraní stanovit a injektovat poskytovatele služeb.
V případě PHP Jet to funguje tak, že se Jet rád přizpůsobí aplikaci (přesněji vývojáři aplikace), vše je možné si uzpůsobit plně ku spokojenosti. Ale Jet stále drží jednotný rámec systému. Stále je garantem služeb. A tak někdo může reimplementovat nějakou systémovou entitu (např. entitu stránek, nebo hlavní router) a ono vše bude fungovat dál. Celek bude držet pohromadě, nerozsype se. (Pokud je ona reimplementace správná – pochopitelně).
PHP Jet v řadě případů přímo vyžaduje, aby si aplikace sama implementovala poskytovatele služeb a ty injektovala do garanta služby. PHP Jet například sám o sobě vůbec nemá implementaci auth kontroleru, ani entity uživatel, role a ani oprávnění, stejně tak žádné loggery. Jet jen a pouze poskytuje rozhraní. Proč? Protože framework opravdu nemůže vědět jaké vlastnosti bude mít třeba uživatel, kde bude vlastně uložen (databázová tabulka opravdu není jediná možnost kde mít uživatele), … Do takových věcí frameworku vlastně nic není. Ten je od toho, aby garantoval základní rámec, existenci určitého subsystému, jeho rozhraní a rozhraní entit do tohoto subsystému náležících. Povšimněte si, že v ukázkové aplikaci jsou věci jako uživatelé a role a auth kontrolery zcela mimo knihovnu, vše je v aplikačním prostoru a plně se očekává, že si to každý upraví jak chce – jsou to pouze ukázky a možný základ od kterého se dá odrazit, ale ne součást frameworku.
A díky vkládání poskytovatelů služeb a systému garantů služeb je extrémně snadné mít například úplně jiný princip autentizace pro REST API, jiný pro web a jiný pro administraci a je možné přidat třeba 100 dalších možností. Vše může být diametrálně odlišné, ale základní rámec garantuje, že základní rozhraní toho všeho bude vždy právě takové a vše se může zeptat zda je vůbec někdo přihlášený. No a pak je snadné dělat takové triky jako přenášet aplikační moduly, opakovaně používat kód na různých místech a tak dále. A mimochodem … Ta podpora REST API je řešená úplně stejně. Opět garant + poskytovatel. O REST API ale bude článek.
Stejně jako aplikace běžící přímo na nějakém OS ví jak zacházet s API a systémovými voláními OS, tak to samé ví i aplikace (mikro-aplikace / aplikační moduly) v PHP Jetu. Vše v projektu ví že existuje garantované rozhraní. A co je za tím rozhraním? Co a jak službu poskytuje? To už si vývojář může nainjektovat dle libosti. No a stejně jako OS někdy potřebuje dodat třeba ovladače, tak v PHP Jet je někdy nutné dodat poskytovatele služeb.
A samotné použití toho všeho je tak jednoduché, že jednodušší způsob asi neznám. Ale zároveň to zachovává veškerou flexibilitu.
Tedy ne, žádné lokátory služeb a podobné věci. Nic takového. Jen naprosto primitivní princip. Nevím zda originální nebo ne a jestli to má nějakou jinou nálepku, to není podstatné.
Ale princip funkční a plnící vše co od něj požaduji. Jakkoliv to nebrání flexibilitě vznikajícího projektu. Naopak. Má to jasně daná pravidla, která je možné vysvětlit při setkání “face-2-face” za 15 minut. Není třeba se učit nic složitého. Technicky je to naprosto triviální. Spotřeba prostředků prakticky veškerá žádná.
Zastávám názor, že správné řešení je to nejjednodušší – které samozřejmě zároveň plní požadovaná kritéria. A to systém garantů služeb je.
Dané řešení, ve spolupráci s továrnami a systémovou konfigurací (náhradou za homady konstant) nic neblokuje, ničemu nebrání. Ba naopak. Dá se dělat cokoliv. Naprosto cokoliv. A při tom je to zcela transparentní a směšně jednoduché.
Je to celé jednodušší než vysvětlit to pomocí textu . Tak jsem to dnes snad napravil a věřím, že kdo chce pochopit, tak pochopí.
Tož tak … Framework už mám.
Nu, a teď se musím naučit jak to vysvětlovat 🙂 Snad se mi to dnes trochu povedlo.
Díky za pozornost a mějte se krásně!
Takže jestli jsem to správně pochopil, autor řekl, že nechce Dependency Injection framework, ale jen Dependency Injection. Pak zaměnil Dependency Injection za Service Locator (což je svým způsobem přesný opak DI) a vymyslel Garanta služby, což je klasický Service Locator, akorát mu tak autor nechce říkat.
Nečetl jsem to zatím celý, ale spíš mi přijde, že ani Service Locator - tohle jsou prachsprostý globální proměnný "schovaný" pomocí static getteru a setteru.
Ano, autor to má statické, takže bude mít nejspíš pro každou instanci totožné service zvláštní třídu Garanta. Čili pokud by měl za úkol například kopírovat soubory z ftp seznamu na ftp roota, tak by zavedl garanty FtpRootGarant a FtpSeznamGarant a ty používal... :-D :-D :-D
Zatímco zbytek světa si v libovolném DI frameworku instancuje dvě instance ftp klienta a ty si nechá prostě vložit na potřebná místa aplikace. Což mi mimochodem přijde i pro začátečníka dostatečně srozumitelné.
Přitom by stačilo, aby autor zkonzultoval svoje nápady a někým, kdo tomu rozumí... K předchozímu dílu a náročnosti routování se radši ani nevyjadřuju.
Nejjednodušší na použití je třeba ve vstupním index.php :
$auth = new Auth_Controller(); // flexibilně if-elseif-ovat, pokud jsou různé třídy pro různé kontexty …
A pak podle potřeby bez statických metod (!)
global $auth;
$auth->checkCurrentUser();
Ale to je ne-DI, čili dle současného bontonu fuj :)
Přijde mi, že tenhle seriál je v prvé řadě postavený na špatné logice. Tenhle článek to vystihuje - na začátku představí něco úplně špatně a argumentuje, proč je špatně, pak představí ještě jedno řešení, které je dost špatně a následovně logickou úvahou dojde k tomu, že jiné řešení tedy musí být správně a představí to svoje. Takhle logika nefunguje.
Proč jsou statické providery špatně, bylo už diskutováno několikrát, a existuje na to spousta článků. Ano, je to problém v PHP, že vytvoření aplikace pro každý request je drahé. Na druhé straně existují řešení - design patterny proxy a lazy loading. Čekal bych, že PHP frameworks budou používat něco podobného, ale znalec toho světa moc nejsem.
Moderní PHP FW to běžně používají. Nette a Symfony drží celkem slušný standard v tom, jak se věci mají dělat podle zkušeností, které jsme jako ajťáci získali za celou historii naší profese. Není to problém jazyka nebo PHP světa. Stejně jako filtruju odpad v Javě, C#, Rustu, tak filtruju odpad v PHP.
Pak jsou samozřejmě projekty, které jsou z různých důvodů legaci. Někdy proto, protože existují už dlouho a tak si to sebou táhnou. Někdy proto, že autoři prostě nemají zkušenosti - příklad Davida Grudla před patnácti lety (Nette 0.9 používalo SL), nebo Mirek Marek aktuálně. David se během těch patnácti let posunul, a nyní patří mezi lídry PHP komunity. Přeji mu, aby se to Mirkovi Markovi časem podařilo taky.
Tak to aj v PHP praxi vyzera. Mnozstvo ludi ci su seniori len vdaka tomu ze niekde patlali kod viac ako 3 roky.
Early loading (koli DI cez konstruktory) je pouzitelny akurat tak pre male ptojekty, kde sa paralelne nespracovava mnozstvo requestov. Jadro totiz nemoze alokovat pamat pre viacero procesov sucastne, iba pre jeden, ostatne cakaju na zamok. To vo vysledku sposobi ze request rychly v dev(necaka na zamky) na servri da odpoved v radovo sekundach. A zakaznici neradi vidia ze im google vyrazne penalizuje rate. Lenze lopaty aj tak odmietaju lazy loading, pretoze chyba nie je medzi klavesnicou a stolickou, ale logicky v PHP.
Mě pořád fascinuje, jak ostatní označujete za lopaty, ale sám tady píšete naprosté nesmysly.
Jádro opravdu nemá úzké hrdlo v jednom zámku na alokaci paměti. Ve skutečnosti drtivá většina alokace paměti vůbec ani nejde do jádra. A ani v rámci jednoho procesu tam není jeden zámek - moderní alokátory mají různé per thread nebo per core pools apod - https://en.wikipedia.org/wiki/C_dynamic_memory_allocation#jemalloc .
Zpomalení na produkci je typicky způsobeno větším množstvím dat a jejich neefektivním zpracováním (špatná datová struktura, neefektivní vyhledávání, chybějící indexy v DB apod), případně resource contention v databázi či jinde. Zrovna lazy loading se projeví lineárně stejně v dev či produkci.
Bavime sa o posix systemoch s jadrom beziacimv ring 0. Ste tak blby ze si ani nedokazete precitat co linkujete?
To je neuvěřitelná snůška blbostí, opět jsi nezklamal :-)
Za prvé - žádnej garbage-collected jazyk, PHP nevyjímaje, opravdu kvůli každýmu new nevolá malloc nebo podobně. Alokujou si větší kusy předem a s těma si pak hospodaří samy.
Za druhé - to, jestli se ty věci vytvoří všechny najednou nebo lazy v průběhu requestu, je snad jedno? Dřív nebo pozdějc se ten new zavolat musí. Nebo jako všechny requesty přijdou úplně najednou, takže v prvním případě se čeká na zámek a v druhým ne?
Pořád trochu doufám, že jsi troll, protože možnost, že někde reálně běží software, kterej jsi napsal, je docela děsivá...
Pamet sa alokuje po blokoch aj pre procesy ktore nemaju garbage collector, akurat server koli moznemu velkemu poctu subezne beziacich procesov tie bloky nealokuje prilis velke, to by za chvilu dosla pamat. K tomu aby garbage collector fungoval, je nutne co najviac pamate alokovat v lokalnom kontexte ulohy, nie pred alokaciou objektu, je to takmer rovnaky fail ako globalny kontext alebo singleton(casto vyuzivany pre facade). Taktiez je nutne pribezne odosielanie obsahu klientovi a dealokacia prislusnych bufrov, tie tiez zaberaju pamat.
Nie je to jedno. Meria sa cas od odoslania requestu po pripojenie na server. Google a pravdepodobne s tym zacnu aj dalsi, meria cas od pripojenia na server po prvu odpoved so servra. Je teda daleko vyhodnejsie odosielat obsah asap, ako ho odosielat az na koniec, po tom co sa vykona zazrazny kod nejakeho mentalneho atleta. Potom sa recykluje uvolnene miesto a nie je nutne v ramci requestu alokovat dalsiu a dalsiu pamat.
New sa musi zavolat tak ci tak. Ale pri lazy loadingu sa vola len ten nevyhnutny new, kdezto ked pouzijete DI cez konstruktor, tak musite alokovat vsetky objekty ktore ma ten konstruktor dostat, vratane tych ktore sa realne nepouziju.
Casto dostavam ponuky typu "ono nam to funguje, len to treba doladit a opravit". To desive pre vas moze byt len to, ze po par kolach konzultacii budete mat vela volneho casu.
Přečteno 20 712×
Přečteno 18 551×
Přečteno 17 778×
Přečteno 17 524×
Přečteno 16 220×