Bug in IE9: twice sent cookie is removed
I found a strange bug in Internet Explorer 9, which appears in the last version 9.0.8112.16421. If the same cookie is sent twice, once with the expiration at the end of the session and once with any other expiration, IE9 will remove cookie. Webpage must have a strict doctype.
Example:
<?php
// expire at the end of the session
setcookie('test', 'value', 0);
// expire in 1 hour
setcookie('test', 'value', 3600 + time());
// switch browser and document mode to IE9
?>
<!doctype html>
Is cookie set? <?php echo isset($_COOKIE['test']) ? 'yes' : 'no' ?>
Try to open this example in IE9 and refresh: cookie will not be set.
Solution is to remove older cookies after sending new one:
$headers = array();
foreach (headers_list() as $header) {
if (preg_match('#^Set-Cookie: .+?=#', $header, $m)) {
$headers[$m[0]] = $header;
header_remove('Set-Cookie'); // requires PHP 5.3 or newer
}
}
foreach ($headers as $header) {
header($header, FALSE);
}
Bug is fixed in Nette Framework.
Dependency Injection versus Service Locator
Když se mluví o Dependency Injection, bývá zmiňován i service locator, jako jakési zlé dvojče. O co se vlastně jedná?
Dependency Injection jsem v prvním článku popsal jako princip, kdy se každá třída otevřeně hlásí ke svým závislostem v konstruktoru nebo jiné metodě, místo toho, aby je někde v těle pokoutně získávala z globálního přístupového bodu. Dodržování tohoto principu vede ke srozumitelnějšímu a lépe použitelnému kódu.
Nicméně na vás číhá nástraha v podobě service locatoru.
Stavět proti sobě DI a service locator je nepřesné, protože to první je programátorská technika, zatímco service locator je velechytrý objekt, který umí vrátit veškeré závislosti, které třída potřebuje, nebo i nepotřebuje. Pokud by si všechny třídy předávaly service locator, předaly by si tak v pouhém jediném parametru všechny závislosti. A nemusely by je čarovat z globálních přístupových bodů:
class Authenticator
{
private $locator;
function __construct(ServiceLocator $locator)
{
$this->locator = $locator;
}
function authenticate($user, $pass)
{
$database = $this->locator->getDatabase();
$database->query(...)
}
}
Bohužel tato technika není v souladu s DI. Proč?
Není v souladu s tím, že se třída ke svým závislostem otevřeně
hlásí. Třída Authenticator
- potřebuje databázi, ale hlásí se k velmi obecnému service locatoru, což je v naprostém nepoměru vůči tomu, co skutečně potřebuje
- že potřebuje zrovna databázi se nedá zjistit jinak, než zkoumáním její implementace
Třída se tedy musí hlásit ke všem svým závislostem a právě jen k nim. Jinak o svých závislostech lže.
Service locator proto nebudeme třídám nikdy předávat. Připravili bychom se tak o výhody DI.
(Nicméně může nastat situace, kdy požadovat service locator je korektní: pokud ho třída skutečně jako takový potřebuje. Třeba kvůli jeho vizualizaci apod.)
Co naopak service locator není
Občas někdo pojmenuje validní konstrukci termínem service locator a na základě toho ji odsoudí. Podobný styl uvažování je až nebezpečně zavádějící.
Správné je si uvědomit, co je špatného na service locatoru, tj. proč jej řadíme mezi antipatterny, a ověřit, jestli naše konstrukce netrpí stejnými vadami. Tedy diskuse o tom, zda jde o service locator nebo ne, je podružná.
Jako příklad si ukažme refaktoringu třídy Foo se třemi závislostmi:
class Foo
{
function __construct(A $a, B $b, C $c)
}
$foo = new Foo($a, $b, $c);
Závislosti vytkneme do jedné (immutable) třídy:
class FooDependencies
{
function __construct(A $a, B $b, C $c)
}
class Foo
{
function __construct(FooDependencies $d)
}
$foo = new Foo(new FooDependencies($a, $b, $c));
Důležité je, že z hlediska DI jsou obě alternativy korektní, třídy se otevřeně hlásí ke všem svým závislostem a právě jen k nim. Neplatí tedy námitky, které jsme použili proti service locatoru.
Samozřejmě je otázka, zda uvedený refactoring byl opodstatněný a správný, ale to už je jiný příběh.
Jiný příklad jsem použil v článku Dependency Injection není žádná
věda, kde jsem skupinu závislostí třídy Article zredukoval
na továrničkou ArticleFactory, abych kód konsolidoval a zbavil
se závislostí, které nebyly přímo její, ale někoho jiného. Nebo obdobný
příklad v článku Dependency Injection versus
Lazy loading, kde Authenticator byl nahrazen za
AuthenticatorAccessor, neboť presenter chtěl službu získat
skrze lazy loading. Nic z toho nevykazuje negativní rysy, jaké jsme si
ukázali u service locatoru.
Pokud si nejste jisti, zda používáte korektní objektový návrh v souladu s DI, udělejte si test „slepým API“. Skryjte si těla metod nebo vygenerujte API dokumentaci a závislosti musí být stále jednoznačně patrné.
Všechny části:
- Dependency Injection není žádná věda
- Dependency Injection versus Lazy loading
- Dependency Injection versus Service Locator (právě čtete)
- Dependency Injection a předávání závislostí
- Dependency Injection a property injection
Dependency Injection versus Lazy loading
Lazy loading je návrhový vzor, který odkládá vytváření objektů až do okamžiku, kdy je aplikace skutečně potřebuje. Jak to skloubit s Dependency Injection, které naopak rádo objekty získává už v konstruktorech?
Jak jsme si řekli minule, Dependency Injection je princip, kdy se každá třída ke svým závislostem otevřeně hlásí v inicializačních metodách, obvykle přímo v konstruktoru.
Mějme SignPresenter, který zobrazuje a obhospodařuje formulář pro
přihlašování se do aplikace. Po odeslání formuláře se zavolá metoda
formSubmitted(), která ověří přihlašovací údaje pomocí
autentikátoru. Zjednodušený kód by vypadal takto:
class SignPresenter
{
function formSubmitted()
{
// $GLOBALS['authenticator']->authenticate(...)
Registry::getAuthenticator->authenticate(...)
}
}
V souladu s principem DI si nebudeme autentikátor čarovat z globálního prostředí, ale přiznáme se k této závislosti v konstruktoru:
class SignPresenter
{
private $auth;
function __construct(Authenticator $auth)
{
$this->auth = $auth;
}
function formSubmitted()
{
$this->auth->authenticate(...);
}
}
V praxi však narazíme na zásadní problém: na jedno odeslání formuláře bude připadat třeba 1000 jeho zobrazení. Nicméně autentikátor se bude inicializovat vždy. A jelikož ověřuje vůči databázi, bude v souladu s DI vyžadovat v konstruktoru objekt PDO, jehož vytvoření způsobí připojení se k databázovému serveru.
Tedy každé zobrazení formuláře bude doprovázeno načtením tříd v 99.9 % případů nepotřebných, vytvářením nepotřebných objektů a zbytečným připojením k databázi.
To je závažný nedostatek, který nám vyřeší právě lazy loading.
Jednou z možností je vytvořit tzv. proxy, objekt implementující rozhraní
Authenticator a obalující původní autentikátor, který jej
ovšem instancuje až při zavolání metody authenticate(). Druhou
možností, která však vyžaduje změnu presenteru, je si místo
autentikátoru předat továrničku, která nám jej vyrobí později:
class SignPresenter
{
private $authFactory;
private $auth;
function __construct(AuthenticatorFactory $authFactory)
{
$this->authFactory = $authFactory;
}
function getAuthenticator()
{
if (!$this->auth) {
$this->auth = $this->authFactory->create();
}
return $this->auth;
}
function formSubmitted()
{
$this->getAuthenticator()->authenticate(...);
}
}
interface AuthenticatorFactory
{
/** @return Authenticator */
function create();
}
Metoda getAuthenticator() zajišťuje, že budeme ve třídě
SignPresenter pracovat s jedinou instancí autentikátoru.
A tady by mohl článek skončit. Jenže nekončí.
Továrničku nebrat!
Zdálo se vám použití továrničky jako dobrý nápad či jako úplná samozřejmost? Pak na chvíli zbrzděte.
Zkuste se zamyslet nad rozdílem mezi:
- budu potřebovat objekt získat
- budu potřebovat objekt vyrobit
My objekt budeme potřebovat získat, což je obecnější formulace než vyrobit.
Vraťme se k prvnímu DI příkladu, kde získáváme autentikátor přes konstruktor. Co říká hlavička konstruktoru? Že se mu má předat autentikátor. Nikoliv, že se má vyrobit a poté předat. Metoda, která vytváří instanci SignPresenter, může autentikátor získat jakýmkoliv způsobem (a třeba jej vyrobit), ale samotnému presenteru je po tom kulový. Ten ho jen vyžaduje a neptá se po původu.
Jenže řešení s továrničkou kromě podpory lazy loadingu předjímá i způsob získávání objektu: bude se vyrábět. Takže zatímco v prvním případě dostává SignPresenter jeden autentikátor, ve druhém případě dostává nástroj, kterým si může vyrobit autentikátorů více. Ale o to nám nešlo. Nestojíme před potřebou vyrábět autentikátory, řešíme potřebu lazy-loadingu jediného autentikátoru.
Zdánlivě korektní nasazení továrny je chybné, za chvíli se k tomu ještě vrátím. Správné řešení je místo továrny předat něco, co nám autentikázor později vrátí (ne nutně vyrobí). Říkejme tomu třeba Getter nebo Accessor (v žádném případě nejde o Service locator):
class SignPresenter
{
private $authAccessor;
function __construct(AuthenticatorAccessor $authAccessor)
{
$this->authAccessor = $authAccessor;
}
function formSubmitted()
{
$this->authAccessor->get()->authenticate(...);
}
}
interface AuthenticatorAccessor
{
/** @return Authenticator */
function get();
}
Kód presenteru se nám navíc zjednoduší, protože nepotřebujeme metodu
getAuthenticator(). Samotný accessor totiž zajišťuje, že
pracujeme stále se stejnou instancí.
Jak AuthenticatorFactory tak AuthenticatorAccessor
uvádím jako rozhraní, neboť na implementaci vůbec nezáleží.
Zkusme se podívat, jak může v praxi vypadat kupříkladu testování presenteru:
// vyrobíme si mockovaný autentikátor
$auth = ...;
// a potřebujeme ho dostat do presenteru
$presenter = new SignPresenter(new TrivialAuthenticatorAccessor($auth));
kde TrivialAuthenticatorAccessor je vskutku triviální:
class TrivialAuthenticatorAccessor implements AuthenticatorAccessor
{
private $instance;
function __construct(Authenticator $instance)
{
$this->instance = $instance;
}
function get()
{
return $this->instance;
}
}
Pokud bychom místo accessoru šli původně navrhovanou cestou továrničky,
měli bychom docela problém, jak $auth do presenteru propašovat.
(Mimochodem ukázkový příklad, jak testování vede k lepšímu
návrhu kódu).
Přičemž jakoukoliv továrničku lze do podoby accessoru snadno
přetransformovat, například pomocí obecného
CallbackAccessor:
abstract class CallbackAccessor
{
private $instance;
private $callback;
function __construct(/*callable*/ $callback)
{
$this->callback = $callback;
}
function get()
{
if (!$this->instance) {
$this->instance = call_user_func($this->callback);
}
return $this->instance;
}
}
Jakýkoliv callback pak můžeme přetavit do podoby AuthenticatorAccessor:
class CallbackAuthenticatorAccessor extends CallbackAccessor implements AuthenticatorAccessor
{}
$presenter = new SignPresenter(new CallbackAuthenticatorAccessor(function(){
return ...;
}));
V jednom z příštích dílů se podíváme na automatizovanou podpory lazy loadingu v Nette Framework.
Všechny části:
- Dependency Injection není žádná věda
- Dependency Injection versus Lazy loading (právě čtete)
- Dependency Injection versus Service Locator
- Dependency Injection a předávání závislostí
- Dependency Injection a property injection
Dependency Injection není žádná věda
Dependency Injection je stále pro mnohé obestřeno závojem tajemstvím. Programátoři za tímto návrhovým vzorem hledají složitosti, které tam nejsou. Nejspíš stále chybí zdroj, který by dokázal princip srozumitelně vysvětlit. Přihazuji proto další polínko.
(Článek volně vychází z dokumentace Dependency Injection na stránkách Nette Framework.)
Smyslem Dependency Injection (dále jen DI) je odebrat třídám zodpovědnost za získávání objektů, které potřebují ke své činnosti. Řečeno jasně a srozumitelně: pokud píšete třídu, která potřebuje ke své činnosti databázi, nebudete uvnitř třídy vymýšlet, odkud databázi vzít (tj. žádné globální proměnné, statické metody, singletony, registry atd.), ale otevřeně o ni požádáte v konstruktoru nebo jiné metodě.
A to je vše. To je celé slavné DI.
A k čemu je to dobré? Díky tomu se kód snáze programuje a lépe
chápe. Zkusme si to ukázat na příkladu třídy Article
reprezentující článek na blogu. Její použití je následující:
$article = new Article;
$article->title = '10 Things You Need to Know About Losing Weight';
$article->content = 'Every year millions of people in ...';
$article->save();
Podobných ukázek jste už jistě viděli tisíce. Takto fungují
nejrůznější ORM. Metoda save() nám článek uloží. Kam? Asi
do databázové tabulky. Skutečně? Co když ho uloží do souboru na disk?
A pokud do databáze, tak do jaké tabulky? K jaké databázi se vlastně
připojí? Ostré nebo testovací? Pod jakým účtem?
Mnoho otázek, které naznačují, že kód není dostatečně samovysvětlující.
Museli bychom se podívat, jak je implementovaná metoda save(),
abychom zjistili, kam se data ukládají. Zjistili bychom, že si šahá do
nějaké globální proměnné udržující databázové spojení. Museli bychom
pátrat dál, kde se v kódu databázové spojení vytváří, a pak bychom
teprve měli obrázek o tom, jak vše funguje. To rozhodně není
ideální.
Navíc, pokud bychom chtěli třeba za účelem testování uložit článek jinam, není vůbec jisté, zda by to šlo.
Podívejme se na implementaci třídy Article:
class Article
{
public $id;
public $title;
public $content;
function save()
{
// uložíme do databáze
// …ale kde databázové spojení seženu?
// GlobalDb::getInstance()->query() ?
}
}
Autor metody save() musí řešit nelehkou otázku, kde vzít
připojení k databázi. Kdyby použil DI, nemusí nad ničím uvažovat (a to
mají programátoři rádi), neboť DI dává jasnou odpověď: „nic
nesháněj, ať se postará někdo jiný.“ Jinými slovy, pokud potřebuji
databázi, ať mi ji někdo dodá:
class Article
{
public $id;
public $title;
public $content;
function save(Nette\Database\Connection $connection)
{
$connection->table('articles')->insert(array(
'title' => $this->title,
'content' => $this->content,
));
}
}
Takže aplikace principů DI znamená jen to, že jsme předali
$connection jako parametr metodě? Jako vážně? Ano.
Jako vážně.
Užití třídy Article se pochopitelně nepatrně změní:
$article = new Article;
$article->title = ...
$article->content = ...
$article->save($connection);
Díky této změně je nyní z kódu naprosto zřejmé, že se článek
uloží do databáze, a taky do které databáze. V refaktoringu bychom mohli
pokračovat ve stejném duchu dál a eliminovat závislost Article
na databázi, protože objektový model by se dal nepochybně navrhnout lépe.
Ale to už jde nad rámec článku: DI neznamená kvalitní návrh aplikace, je
jen jeho nezbytnou součástí.
Řešení pomocí DI tak přestavuje win-win situaci: autor třídy Article nemusel řešit, kde objekt-databázi sežene, její uživatel nemusel pátrat, kde ho programátor sehnal. Z kódu je nyní zřejmé, že článek se uloží do databáze a lze velmi snadno nechat jej uložit do databáze jiné.
Můžete ale přijít s celou řadou námitek. Kde se třeba vezme
v posledním příkladu proměnná $connection? DI opakuje
odpověď: „ať se postará někdo jiný“. Databázové spojení zkrátka
dodá ten, kdo zavolá uvedený kód. A tak dále, a tak dále.
Pak lze namítnout, že používání DI kód značně zkomplikuje, protože
kvůli vytvoření instance Article musíte uchovávat a předávat
databázové spojení a podobně. To je pravda. Navíc časem může ve třídě
Article vzniknout potřeba nějaká data cachovat a v souladu
s DI bude potřeba předávat ještě úložiště cache, stejně tak může
Article projít refaktoringem a databázi nahradit obecnější
repozitář. To by znamenalo upravit aplikaci na mnoha místech: přinejmenším
všude, kde se vytváří instance Article.
Co s tím? Věc má řešení. Místo ručního vytváření objektů
Article si vytvoříme továrničku, která bude objekty
Article vyrábět. A místo všech závislostí třídy
Article si tak budeme předávat jen jeho továrničku: (Krom toho
upravíme Article tak, aby se spojení předávalo už v konstruktoru):
class ArticleFactory
{
private $connection;
function __construct(Nette\Database\Connection $connection)
{
$this->connection = $connection;
}
function createArticle()
{
return new Article($this->connection);
}
}
Když se změní konstruktor třídy Article, upraví se jen
továrnička, nic víc.
V praxi se ukazuje, že každá třída mívá jen několik málo závislostí, které ji předáváme. Důsledné používání DI tak navzdory obavám kód nijak výrazně nekomplikuje a zmíněné výhody jednoznačně převažují. Tohle tvrzení neumím nijak dokázat, jde jen o zkušenost.
Lze také namítnout, že netransparentní chování původní třídy
Article, která článek uložila neznámo kam, vlastně vůbec
nevadí, pokud ho metoda load() zase bude umět načíst. Vtip je
v tom, že nad kódem navrženým podle DI principů vždycky můžeme takto
fungující obálku vytvořit. Ale naopak toho docílit nelze.
DI je technika delegující zodpovědnost za získávání objektů vždy o úroveň výš. Nelze ji ale delegovat do nekonečna. Musí být nějaký počátek všehomíra. Úplně na začátku je stvořitel, který už nic nedeleguje a objekty rovnou tvoří. Celý strom závislostí se tak sestavuje z jednoho místa.
Stvořitele lze implementovat jako takzvaný DI kontejner. Ale o tom si řekneme něco příště.
Všechny části:
- Dependency Injection není žádná věda (právě čtete)
- Dependency Injection versus Lazy loading
- Dependency Injection versus Service Locator
- Dependency Injection a předávání závislostí
- Dependency Injection a property injection
Diskuse ke článku o lazy loadingu
Pod článkem Dependency Injection versus Lazy loading se vyrojilo velké množství komentářů, dle mého však nešlo o přínosnou diskusi, ale spíš o nepochopení a špičkování se bez jakýchkoliv hmatatelných argumentů. Aby neubližovaly samotnému článku, přenesl jsem je pod tento spot.
Lazy loading je technika, která musí být (stejně jako jakákoliv jiná technika!) pochopitelně použita jen tam, kde je pro ni vhodné místo. Nebylo předmětem článku tohle řešit (vydalo by to možná na celý seriál). Reálným problémem, kterému programátor přecházející od singletonů a Service Locatorů k Dependency Injection čelí, je, jak vůbec lazy loading použít, pokud jej skutečně potřebuje. Service Locator ho totiž nabízí by design. Na což právě zmíněný článek dává odpověď.
novější články
