phpFashion

Na navigaci | Klávesové zkratky

Objeví Rails Dependency Injection?

Málokdo má takovou potřebu zdůrazňovat svou domnělou nadřazenost, jako právě Railisti. Abyste mě nechápali špatně, jde o dobrou marketingovou strategii. Nepříjemné je, když jí podlehnete do té míry, že zbytek světa vnímáte jen jako upachtěné kopírovače bez šance se vám někdy přiblížit. Svět takový totiž není.

Příkladem je Dependency Injection. Zatímco lidé kolem PHP nebo JavaScriptu objevili DI se zpožděním, Ruby on Rails zůstávají dosud nepolíbené. Bylo mi záhadou, proč framework s tak pokrokovou image zůstává kdesi pozadu a začal v tom pátrat. Odpověď mi dala řada zdrojů na Google nebo karmiq a zní:

Ruby je tak dobrý jazyk, že vůbec Dependency Injection nepotřebuje.

Fascinující argument, který je navíc v elitářském prostředí sebepotvrzující. A je skutečně pravdivý? Nebo jde jen o zaslepení pýchou, stejné zaslepení, jaké způsobilo nedávno přetřásané bezpečnostní díry v Rails?

Říkal jsem si, že je možné, že Ruby znám natolik málo, aby mi nějaký klíčový aspekt unikl, a že skutečně jde o jazyk, který DI nepotřebuje. Jenže primárním smyslem Dependency Injection je zřejmé předávání závislostí, aby byl kód srozumitelný a předvídatelný (a je pak i lépe testovatelný). Jenže když se podívám do dokumentace Rails na tutoriál „blog za pár minut“, vidím tam třeba:

def index
  @posts = Post.all
end

Tedy pro získání blogpostů používají statickou metodu Post.all, která odněkud (!) vrátí seznam článků. Z databáze? Ze souboru? Vyčaruje je? Nevím, protože se tu nepoužívá DI. Místo toho se tu vaří nějaké statické peklo. Ruby je bezesporu šikovný jazyk, ale DI nenahrazuje.

V Ruby lze za běhu přepisovat metody (Monkey patch; obdobně jako v JavaScriptu), což je forma Inversion of control (IoC), která třeba pro potřeby testů dovolí podstrčit jinou implementaci statické metody Post.all. Tohle ale nenahrazuje DI, kód to zřejmější neudělá, spíše naopak.

Mimochodem, zaujala mě i třída Post tím, že reprezentuje jak jeden článek na blogu, tak funguje jako repozitář (metoda all), což je porušení Single Responsibility Principle jako vyšité.

Jako odůvodnění, proč Ruby nepotřebují DI, se často odkazuje na článek LEGOs, Play-Doh, and Programming. Důkladně jsem ho pročetl, sledoval, jak autor párkrát zaměňuje „DI“ s „DI frameworkem“ (tedy něco jako zaměňovat „Ruby“ s „Ruby on Rails“) a nakonec zjistil, že k závěru, že Ruby Dependency Injection nepotřebují, vůbec nepřišel. Psal, že nepotřebují DI frameworky, jaké zná z Javy.

Jeden mylně interpretovaný závěr, pokud pohladí ego, dokáže zcela pobláznit obrovskou skupinu inteligentních lidí. Nakonec mýtus, že špenát obsahuje neobyčejné množství železa, se taky drží od roku 1870.

Ruby je velmi zajímavý jazyk, vyplatí se v něm jako v každém jiném používat DI a existují pro něj i DI frameworky. Rails je zajímavý framework, který zatím neobjevil DI. Až ho objeví, půjde o velké téma některé z příštích verzí.

(Po pokusu diskutovat o DI s Karmim, kterého pokládám za nejinteligentnějšího Railistu, nechávám komentáře uzavřené, omlouvám se.)

Viz také: Dependency injection není pouze o jednoduším testování

před 3 lety v rubrice Web | shlédnuto 15898×


PHP 5.4 má nepoužitelný typ callable

Jednou z novinek PHP 5.4 je typehint callable. Byla by to parádní věc, kdyby to tvůrci tak děsně nezprasili.

PHP typ callbable je pseudotypem, jehož hodnotou může být buď název metody či funkce (tj. řetězec) nebo dvojice třída/objekt a její metoda, tedy pole. V PHP se používá od pradávna, nicméně od verze 5.4 pro něj existuje typehint:

class Template
{
    function registerHelper($name, callable $helper)
    {
        ...
    }
}

$template = new Template;
$template->registerHelper('date', 'Helpers::date');

Skvělé. Tedy až do chvíle, než to vyzkoušíte a PHP vás zdupne:

error:  Argument 2 passed to Template::registerHelper() must be callable, string given

Aha, tak je to asi potřeba zapsat jako pole:

$template->registerHelper('date', ['Helpers', 'date']);

Výsledek je opět:

error: Argument 2 passed to Template::registerHelper() must be callable, array given

Ve skutečnosti PHP vadí to, že třída Helpers neexistuje. Totiž už během volání registerHelper její přítomnost vyžaduje. Typehint callable, narozdíl od všech jiných typehintů, neověřuje jen formální platnost předaného argumentu, ale ujišťuje se, že uvedená třída skutečně existuje a má zmíněnou metodu. Pokud třída neexistuje, pokusí se ji načíst autoloadingem.

Což jednak zabíjí lazyloading – už samotné předání parametru s callbackem načte příslušnou třídu, která by se třeba jinak vůbec načítat nemusela.

A za druhé to generuje naprosto idiotské chybové hlášky. Což je věc, na kterou jsem dosti citlivý. Ze zprávy must be callable, string given těžko někdo pochopí, že typ string je použitý správně, jen neexistuje třída či metoda. Já bych to chápal tak, že funkce neakceptuje řetězce. Přitom třeba funkce call_user_func ve stejné situaci generuje zcela smysluplné chyby jako:

Warning: call_user_func() expects parameter 1 to be a valid callback, class 'Helpers' not found

Warning: call_user_func() expects parameter 1 to be a valid callback, class 'Helpers' does not have a method 'date'

Na chybu jsem upozornil, bohužel Rasmus je se současným chováním nadmíru spokojen. Setrvávat na zavádějících chybových hláškách je projevem arogance a hlouposti: namísto opravy raději uvedou ve zmatek statisíce programátorů a připraví je o spoustu času. Nette Framework rozhodně zprasený typ callable používat nebude.


Monkey patching v PHP

PHP přistupuje ke třídám způsobem známým ze staticky typovaných jazyků a neumožňuje monkey patching, tedy měnit za běhu metody tříd, kopírovat je mezi instancemi a podobně.

Abyste porozuměli, co mám na mysli, vytvořme třídu Greeting s metodou say():

class Greeting
{
    function __construct($name)
    {
        $this->name = $name;
    }

    function say($message)
    {
        echo "$message $this->name.";
    }
}

$g = new Greeting('John');
$g->say('Hello'); // Hello John.

V PHP neočekáváme, že by bylo možné metodu třeba uložit do proměnné či jiného atributu a poté zase zavolat:

$method = $g->say;
$g->greet = $method;
$g->greet('Hello');

Nebo dokonce za chodu přidávat metody nové:

$g->shout = function($message) {
    echo "$message $this->name!!!";
};

$g->shout('Hello'); // Hello John!!!

Na jedné straně je mi líto, že tohle PHP neumí, na straně druhé vidím ve statickém pojetí tříd podstatné výhody. A na straně třetí: v PHP lze tohle chování snadno emulovat.

Emulace dynamiky

Vytvořit funkci jako je výše uvedená shout() a vložit ji do proměnné objektu PHP už umí od verze 5.3. Ale abychom ji mohli zavolat běžným zápisem, musíme si vypomoci magickou metodou __call():

class Greeting
{
    function __call($name, $args)
    {
        if (!isset($this->$name) || !$this->$name instanceof Closure) {
            throw new Exception("Method $name not found.");
        }
        return call_user_func_array($this->$name->bindTo($this, $this), $args);
    }

    ...
}

A nyní už bude příklad s metodou shout() fungovat.

Abychom mohli stejně nakládat i se statickými metodami, jako byla třeba výše uvedená say(), doplníme ještě __get():

class Greeting
{
    function __get($name)
    {
        if (!method_exists($this, $name)) {
            throw new Exception("Property $name not found.");
        }
        return function() use ($name) {
            return call_user_func_array(array($this, $name), func_get_args());
        };
    }

    ...
}

A nyní bude fungovat i první příklad s přiřazením $method = $g->say a následným voláním.

Pro verzi 5.3

Uvedené příklady vyžadují PHP 5.4. Ve verzi 5.3 jsou closures ořezané a nesmí se v nich používat $this. Řešení by vypadalo trošičku jinak:

// for PHP 5.3

class Greeting
{
    function __call($name, $args)
    {
        if (!isset($this->$name) || !$this->$name instanceof Closure) {
            throw new Exception("Method $name not found.");
        }
        array_unshift($args, $this);
        return call_user_func_array($this->$name, $args);
    }

    function __get($name)
    {
        if (!method_exists($this, $name)) {
            throw new Exception("Property $name not found.");
        }
        return function() use ($name) {
            $args = func_get_args();
            return call_user_func_array(array(array_shift($args), $name), $args);
        };
    }

    ...
}

A namísto $this bychom uvnitř closure použili první argument, pojmenovaný třeba $self.

$g->shout = function($self, $message) {
    echo "$message $self->name!!!";
};

Nicméně narozdíl od 5.4 varianty má nyní funkce přístup jen k veřejným proměnným třídy.


Víte, co znamená $ v regulárním výrazu?

Nejprve otázka: mačne nebo nemačne?

$str = "123\n";

echo preg_match('~^\d+$~', $str);

Kdo z vás si myslí, že funkce vrátí false, protože regulární výraz běží v jednořádkovém režimu a nepovoluje v řetězci žádné jiné znaky krom číslic, ten se mýlil.

Malinko odbočím. Regulární výrazy v jazyce Ruby mají jednu nectnost (nesoulad s de facto standardem PERLu): znaky ^ a $ neoznačují začátek a konec řetězce, ale jen jednoho řádku v něm. Neznalost tohoto faktu může způsobit bezpečnostní zranitelnost, jak třeba upozorňuje dokumentace Rails. PHP se chová standardně, ale málokdo už ví, co přesně ono standardní chování znamená. Dokumentace meta-znaku $ je totiž nepřesná. (už opraveno)

Správně má být, že znak $ znamená konec řetězce nebo ukončující odřádkování; ve víceřádkovém režimu (modifikátor m) znamená konec řádku.

Skutečný konec řetězce chytá sekvence \z. Nebo je možné použít dolar společně s modifikátorem D.

$str = "123\n";
echo preg_match('~^[0-9]+$~', $str); // TRUE
echo preg_match('~^[0-9]+$~D', $str); // FALSE
echo preg_match('~^[0-9]+\z~', $str); // FALSE

DI a property injection

Dependency Injection je zřejmé předávání závislostí, tedy že se každá třída otevřeně hlásí ke svým závislostem, místo toho, aby je někde pokoutně získávala. Co kdyby se závislosti předávaly přímo do proměnných? Proberu úskalí a výhody property injection.

Property injection má jednu podstatnou výhodu: stručnost. Srovnejte:

class Foobar
{
    /** @var HttpRequest */
    private $httpRequest;

    /** @var Router */
    private $router;

    function __construct(HttpRequest $httpRequest, Router $router)
    {
        $this->httpRequest = $httpRequest;
        $this->router = $router;
    }

}

versus

class Foobar
{
    /** @var HttpRequest @inject */
    public $httpRequest;

    /** @var Router @inject */
    public $router;

}

Proměnné musíme definovat tak jako tak. Zpravidla u nich uvádíme i anotaci @var a příslušný datový typ. Je lákavé si ušetřit práci a místo psaní rutinního kódu konstruktoru nebo metody inject() doplnit prosté @inject. Property injection kromě minimálního režijního kódu navíc parádně řeší problém s předáváním závislostí a dědičností.

Použití anotace představuje jinou konvenci pro předání závislostí. Zdůrazňuji slovo jinou, protože ať už vyjmenujeme závislosti jakožto argumenty metody nebo anotováním, jde o ekvivalentní činnost. Čímž oponuji názoru, že použití anotace představuje závislost na kontejneru. To v žádném případě není pravda, jde jen o konvenci, koneckonců dosud o kontejnerech nepadla řeč a ukázky dávají smysl.

Stejně tak se nedívejte na anotaci @inject jako nějakou odpornou magii, kterou musíte nastudovat, abyste ji mohli používat. Žádná magie tu není. Jde o obyčejné veřejné proměnné a anotace je jen doplňující informace pro programátora, říkající, že objekt vyžaduje tyto proměnné naplnit. (Nutno dodat, že Jakub Vrána reagoval na použití anotací u private proměnných, což magie je.)

V článku o předávání závislostí jsem se používání proměnných širokým obloukem vyhnul, protože mají vážné nedostatky:

  • public proměnné nezajistí typovou kontrolu
  • public proměnné nezajistí neměnnost
  • private proměnné nelze naplnit žádnou jazykovou konstrukcí
  • private proměnné nejsou součástí veřejného API – nejde tedy o deklaraci závislosti!
  • pro protected proměnné platí nevýhody obou

Ještě bych přidal, že anotace nejsou nativní součástí jazyka PHP a jde tedy o nestandardní konvenci, oproti třeba injektáži přes konstruktor.

Poznámka: vstřikování závislostí do privátních proměnných posvětila třeba Java EE 6 a je to skutečně ee. Třída své závislosti tají (private = neveřejný) a nelze ji instancovat jinak, než kontejnerem (závislost na kontejneru). Jde zcela proti smyslu Dependency Injection, jak je popsán v perexu tohoto článku, a také proti základnímu principu OOP, zapouzdření. Označil bych to jako „Inversion of Dependency Injection.“

Pro properly property injection bychom potřebovali once-write-only veřejnou proměnnou s typovou kontrolou. Kdyby tohle PHP umělo, nic by nebránilo je používat. Jenže PHP to neumí.

Emulace inject property

PHP to neumí, ale lze to emulovat!

Emulaci zajistíme pomocí magických metod __set a __get. Jak ale dosáhnout toho, aby se k public proměnné přistupovalo skrze tyto metody? Použijeme trik: v konstruktoru ji unsetneme. Proměnná zmizí a při přístupu k ní se již použijí magické metody.

Příklad implementace ve formě základní třídy Object by mohl vypadat třeba takto:

class Object
{
    private $injects = array();

    function __construct()
    {
        // následující analýza proměnných by se mohla kešovat
        $rc = new ReflectionClass($this);
        foreach ($rc->getProperties() as $prop) {
            if ($prop->isPublic() && strpos($prop->getDocComment(), '@inject')
                && preg_match('#@var\s+(\S+)#', $prop->getDocComment(), $m)
            ) {
                // unset property to pass control to __set() and __get()
                unset($this->{$prop->getName()});
                $this->injects[$prop->getName()] = array('value' => NULL, 'type' => $m[1]);
            }
        }
    }


    function __set($name, $value)
    {
        if (!isset($this->injects[$name])) {
            throw new Exception("Cannot write to an undeclared property $$name.");

        } elseif ($this->injects[$name]['value']) {
            throw new Exception("Property $$name has already been set.");

        } elseif (!$value instanceof $this->injects[$name]['type']) {
            throw new Exception("Property $$name must be an instance of {$this->injects[$name]['type']}.");

        } else {
            $this->injects[$name]['value'] = $value;
        }
    }


    function __get($name)
    {
        if (!isset($this->injects[$name])) {
            throw new Exception("Cannot read an undeclared property $$name.");
        }
        return $this->injects[$name]['value'];
    }

}

pak stačí deklarovat výše uvedenou třídu Foobar jako potomka Object a vše bude fungovat standardně podle očekávání:

class Foobar extends Object
{
    /** @var HttpRequest @inject */
    public $httpRequest;

    /** @var Router @inject */
    public $router;

}

$fb = new Foobar;
$fb->router = new Router;

Navíc však máme zajištěnou neměnnost a typovou kontrolu:

$fb->router = new Router;
// Exception: Property $router has already been set.

$fb->httpRequest = new Router;
// Exception: Property $httpRequest must be an instance of HttpRequest.");

Čistá cesta nebo prasárna?

Zkusme se zamyslet nad tím, co vlastně anotace @inject představuje: hint pro programátora, že proměnnou má při vytváření objektu nastavit a že ji později nesmí měnit. Anotace @var pak nařizuje typ.

Je na programátorovi, aby dodržel kontrakt. Stejně jako v případě anotace @private v PHP 4 nebo JavaScriptu, či anotace @return v současném PHP. Jde o pravidla, u nichž se předpokládá, že je programátor dodrží, aniž to lze na úrovni interpreteru ověřit.

Třída Object rozšiřuje PHP o schopnost kontroly za běhu, usnadní tedy identifikaci chyb. Je to vychytávka navíc. Z mého pohledu tedy akceptovatelná cesta k použití property injection v PHP. Možná by se dalo uvažovat nad zařazením do Nette\Object a legitimizace této injektáže v Nette.


Všechny části:


DI a předávání závislostí presenterům

V Nette jsme narazili na zajímavý problém, který úzce souvisí s článkem Dependency Injection a předávání závislostí a komplikacemi způsobenými dědičností.

Stěžejním bodem tvorby aplikací v Nette je vytváření vlastních presenterů, tedy potomků třídy Nette\Application\UI\Presenter (dále Presenter s velkým P). Ta má své závislosti. Byl bych rád, aby uživatelé stavěli aplikace v souladu s DI a tedy se i jejich presentery hlásily ke svým závislostem.

Jenže jak to realizovat? Možná řešení:

  1. Presenter i potomek uvedou závislosti v konstruktoru – to by bylo peklo
  2. pro každou závislost se použije samostatný setter – zbytečně moc režijního kódu
  3. použije se jedna metoda pro závislosti Presenteru a jedna pro potomka
    1. Presenter využije konstruktor, potomek metodu inject()
    2. potomek využije konstruktor, Presenter metodu inject()
    3. nebudeme do toho tahat konstruktor, použijí se jiné metody
  4. použije se třída PresenterDependencies sdružující závislosti do jedné proměnné
  5. injektování přímo do proměnných – nevím, jak pojmout

ad 1) Jak už jsem psal v odkazovaném článku, pokud by Presenter i jeho potomek pro předání závislostí použili konstruktor, musel by se ten překrývající postarat o předání všech rodičovských závislostí. Ale jakmile se rodičovské závislosti změní (refactoring Presenteru), všechny třídy se rozbijí. Neakceptovatelné.

ad 2) Zda předávat všechny závislosti jednou metodou nebo zda vytvořit metodu/injektor pro každou závislost zvlášť, je v principu jedno. Protože nemám rád, když je moc režijního kódu a zbytečně bobtná API, volím raději jednu metodu.

ad 3) Šikovně vypadají přístupy 3a a 3b, tedy použít konstruktor pro závislosti jedné strany a metodu inject() pro závislosti druhé. O tom, který způsob použít, jsme se přeli s Honzou Tichým. On preferoval způsob 3b s tím, že když už nemáme žádné ideální řešení, zvolme z možných to, které bude funkční, pokud uživatel začne věc řešit intuitivně, aniž by věděl o nějaké metodě inject(). A intuitivně použije konstruktor.

Namítal jsem, že ho tak utvrdíme v mylném dojmu, že předek, tedy třída Presenter, vlastně žádné své závislosti nemá, jelikož si programátor v konstruktoru předává jen ty své. Ale jde víceméně o banalitu.

Horší je, že konstruktor potomka je volán nad objektem, který ještě není plně inicializován, tj. nejsou mu předány závislosti rodiče. Zkrátka se volá ještě před voláním inject(). Pokud by uživatel v konstruktoru použil funkci, která nějakou takovou závislost vyžaduje (například getUser()), výsledkem bude zavádějící chybová hláška Fatal Error: Call to a member function xyz on a non-object nebo nějaká podobná. Což není dobře.

Kdyby se uživatel zeptal, jak má tedy situaci vyřešit a v konstruktoru plné inicializace docílit, odpovědí by bylo: nejde to, přesuň kód do metody startup(). (Pokud nejsem srozumitelný, podívejte se na příklad níže.) Tímto problémem řešení 3a netrpí.

ad 5) injektování přímo do proměnných s anotací @inject by bylo vůbec nejšikovnější. Naprosté minimum režijního kódu, konec problémů s dědičností. Má to ale svá úskalí: obsah public proměnných nelze kontrolovat, private proměnné nejsou součástí veřejného API (jak je plnit?) nicméně k tomuto tématu se ještě v nějakém dalším článku vrátím.

Nakonec jsme se s Vaškem Purchartem domluvili na šalamounském řešení 4, kdy se všechny závislosti budou předávat konstruktorem s tím, že závislosti třídy Presenter sbalíme do jediného objektu PresenterDependencies, ten už se bude předávat snadno a při změně závislostí Presenteru se nic nerozbije.

Pokud budu chtít do svého presenteru předat objekt ArticleFacade, bude to vypadat takto:

class ArticlePresenter extends Presenter
{
    function __construct(Nette\Application\UI\PresenterDepedencies $pd, ArticleFacade $af)
    {
        parent::__construct($pd);
        $this->articleFacade = $af;

        // nyní je objekt plně inicializován a lze volat třeba:
        if (!$this->getUser()->isLoggedIn()) { ... }
    }
}

Podstatné je, že všechny závislosti budou předány přímo v konstruktoru, objekt bude ihned inicializován a nebudou se zavádět žádné novoty v podobě metod inject. Jak Vašek psal, jde o ústupek pohodlnosti v zájmu přehlednosti: „Většinou v Nette vyhrávala vždy pohodlnost … mám za to, že v rámci plánovaných změn a refaktoringů by Nette mělo tuhle houpačku mírně zhoupnout směrem k přehlednosti za cenu absolutní pohodlnosti.“

Poznámka: aby bylo evidentní, že třída PresenterDependencies je jen workaround nedostatku v návrhu jazyka a že vlastně nemá sémantický význam v objektovém modelu, nevadilo by mi ji implementovat takto triviálně:

class PresenterDependencies
{
    private $services;

    function __construct(Application\Application $application, ..., Security\User $user)
    {
        $this->services = func_get_args();
    }

    function get()
    {
        return $this->services;
    }
}


abstract class Presenter
{
    function __construct(PresenterDependencies $pd)
    {
        list($this->application, ..., $this->user) = $pd->get();
    }

}

Konec dobrý, všechno dobré. Vašek připravil pull request a já si uvědomil…

Slepá ulička

…že tohle řešení je vlastně nejhorší ze všech. Proč?

Udělal jsem si výčet faktorů, které chci u jednotlivých řešení sledovat (pořadí nesouvisí s důležitostí):

  • pohodlnost
  • intuitivnost
  • blbuvzdornost
  • moment inicializace
  • rozšiřitelnost (dědičnost)

ad pohodlnost: Povinné předání objektu PresenterDependencies je zdánlivá drobnost, ale troufám si tvrdit, že tohle by byl důvod, proč mnozí DI nepoužijí a raději si ArticleFacade vytáhnou ze service locatoru. Uvedený type hint je nejdelší název třídy z celého frameworku a psát ho je za trest.

Představil jsem si sám sebe, jak tohle ukazuju na školení a připadal bych si jako kokot. Jako bych školil nějaký Zend. Musel bych se ptát sám sebe, proč to neděláme nějak lépe.

ad intuitivnost: Honza položil otázkou, co se stane, pokud člověk začne věc řešit intuitivně. Dopadne to špatně. Nenapadne ho, že existuje nějaké PresenterDepedencies a že je ho potřeba předat. Nepředá ho, Presenter nezíská závislosti a bude v poloinicializovaném stavu. V nečekané chvíli vyskočí nějaký divoký nicneříkající Fatal Error.

S tím velmi úzce souvisí otázka blbuvzdornosti. I když budu vědět, že musím volat parent::__construct() (vždycky volejte!), mohu na to snadno zapomenout a bude problém. V případě presenteru by bylo možné (a vhodné) implementovat kontrolu, zda byl volán, stejně jako je tomu u metody startup(), ale problém je, že řešení 4 jde této chybě naproti.

Programátor také může udělat chybu v type hintu (všimli jste si, že jsem ji v příkladu udělal?) nebo jej neuvede vůbec. Sice jsem se snažil poladit chybové hlášky DI kontejneru, ale ty ve své obecnosti nikdy nemohou být dokonalé a programátor bude stejně zmaten.

Pod momentem inicializace myslím okamžik, kdy je objekt plně inicializován. PresenterDependencies ho dokázal dostat do konstruktoru, jiné řešení tohle neumí. Nicméně výhodu v případě presenterů relativizuje fakt, že uživatelé jsou stejně zvyklí úvodní kód dávat do metody startup(), kde už objekt plně inicializován je.

Pátý a dosud opomíjený faktor hodnotí, jak se řešení vypořádá s existencí více úrovní dědičnosti. Což je v případě Nette obvyklé, tj. vytvořit si nějaký abstraktní BasePresenter a teprve od něj dědit. Co když bude mít BasePresenter také nějaké závislosti? Řešení 4 předpokládá, že bychom je také uváděli v konstruktoru třídy BasePresenter a znovu opakovali v jeho potomcích. Otravnost by se tím zvyšovala.

Ven ze slepé uličky

Udělal jsem si tabulku, která u každého řešení uvádí, jak vyhovuje jednotlivým faktorům. Jasným vítězem se ukázalo být 3c. To konstruktor vůbec nepoužívá a každá třída si své závislosti předá metodou (nejlépe finální) s názvem začínajícím na inject. (Viz implementace.)

PresenterFactory, která má za úkol vytvářet instance presenterů, postupuje tak, že po vytvoření objektu zavolá všechny jeho metody inject v pořadí od třídy Presenter k potomkům. Tedy programátor si v každé třídě vytvoří metodu nazvanou inject() nebo podobně, a v ní uvede jednotlivé závislosti. Nemusí předávat nic navíc, minimalizuje se možnost chyby. Z hlediska pohodlnosti, rozšiřitelnosti a blbuvzdornosti je tak učiněno za dost.

Typický příklad předávání závislostí do presenterů bude v Nette vypadat takto:

class BasePresenter extend Presenter
{
    function injectBase(User $user, ...)
    {
        $this->user = $user;
        ...
    }
}

class ArticlePresenter extends BasePresenter
{
    function inject(ArticleFacade $af)
    {
        $this->articleFacade = $af;
    }
}

A co z hlediska intuitivnosti? Co když uživatel použije pro předání závislostí konstruktor, protože o žádných metodách inject() neví? Nevadí, fungovat to bude. Pochopitelně v konstruktoru nebude stále možné volat např. metodu getUser(), ale tuto daň rád zaplatím.

Celá věc by šla zjednodušit injektováním přímo do proměnných, třeba časem k tomu najedeme klíč. Do té doby se mi 3c jeví nejschůdnější.


DI a předávání závislostí

Víte, že Dependency Injection je zřejmé předávání závislostí, tedy že se každá třída otevřeně hlásí ke svým závislostem, místo toho, aby je někde pokoutně získávala. Otázka zní, jak se k nim hlásit a jak je předávat.

K předávání závislostí můžeme využít konstruktor:

class Foobar
{
    private $httpRequest, $router, $session;

    function __construct(HttpRequest $httpRequest, Router $router, Session $session)
    {
        $this->httpRequest = $httpRequest;
        $this->router = $router;
        $this->session = $session;
    }

}

$foobar = new Foobar($hr, $router, $session);

Nebo metody:

class Foobar
{
    private $httpRequest, $router, $session;

    function setHttpRequest(HttpRequest $httpRequest)
    {
        $this->httpRequest = $httpRequest;
    }

    function setRouter(Router $router)
    {
        $this->router = $router;
    }

    function setSession(Session $session)
    {
        $this->session = $session;
    }
}

$foobar = new Foobar;
$foobar->setSession($session);
$foobar->setHttpRequest($hr);
$foobar->setRouter($router);

Nebo přímo naplnit jednotlivé proměnné:

class Foobar
{
    /** @var HttpRequest */
    public $httpRequest;

    /** @var Router */
    public $router;

    /** @var Session */
    public $session;
}

$foobar = new Foobar;
$foobar->session = $session;
$foobar->httpRequest = $hr;
$foobar->router = $router;

Které řešení je nejlepší? Aby článek nebyl neúměrně dlouhý, odkážu se na Předávání závislostí od Vaška Purcharta, tak si jej přečtěte, protože budu navazovat tam, kde končí.

Takže znovu: které řešení je nejlepší? Kdyby byly ekvivalentní, bylo by nejspíš to poslední, protože kód třídy je nejkratší a kratší kód minimalizuje možnost vzniku chyby a šetří čas při psaní i čtení. Nicméně řešení ekvivalentní nejsou. Jsou spíše diametrálně odlišené.

Immutability tedy neměnnost

Immutable object je objekt, který nemění svůj stav od chvíle, co byl vytvořen. Nevěřili byste, kolik problémů objektového návrhu se dá vyřešit jen tím, že se objekty stanou neměnné. Ale to je téma na jiný článek.

Prakticky vždy budeme chtít, aby závislosti objektu byly neměnné. A v tomto směru se jednotlivé varianty předávání liší. Veřejné (public) proměnné můžeme změnit kdykoliv a změnu nelze detekovat, což je zcela diskvalifikuje ze hry a dále už s touto variantou nebudu vůbec počítat. A to ani nemluvím o chybějící typové kontrole. (Viz také úvaha nad tím, jak by se dalo property injection řešit.)

Mohlo by vás napadnout nahradit public za private a vložit do nich závislosti některým z nízkoúrovňových triků (třeba pomocí reflexe), ale takové obcházení vlastností jazyka do obecných úvah o DI nepatří. Privátní proměnné nejsou součástí veřejného API třídy a nelze se jimi hlásit k závislostem. A také nehackujme jazyk, dokud to není nutné.

Neměnnost bychom si u metod mohli zajistit sami:

function setRouter(Router $router)
{
    if ($this->router) {
        throw new InvalidStateException('Router has already been set.');
    }
    $this->router = $router;
}

A protože metoda není klasický obecný setter, tj. lze ji volat jen jednou, nelze očekávat existenci getteru a můžeme její volání považovat za povinné, mohla by používat jiné názvosloví. Například prefix inject, v tomto případě injectRouter().

Vytvořili bychom tedy pro větší srozumitelnost konvenci, že závislosti předáváme metodami inject.

(Musím zdůraznit, že se bavíme o konvenci užitečné pro programátora, o žádných DI kontejnerech v článku nepadlo ani slovo. Pochopitelně by se jí dalo s úspěchem využít i v kontejnerech, nicméně je naprosto zásadní uvědomit si, co je příčinou a co důsledkem.)

Používání metod pro injektáž má svá úskalí:

  • musíme sami zajistit neměnnost
  • špatně se odhaluje okamžik, kdy jsou nastaveny všechny závislosti, abychom provedli inicializaci objektu
  • měli bychom také ověřovat, že se některé závislosti neopomněly nastavit
  • režijní kód bude poměrně ukecaný a dlouhý

Všechny tyto nedostatky řeší už z principu injektáž přes konstruktor, proto vychází jako nejvhodnější.

(…Tedy, ehm, neřeší… Ale k tomu se hnedle dostaneme.)

Constructor hell

Nenápadný problém předávání závislostí přes konstruktor tkví v tom, že nemáme žádné vodítko, v jakém pořadí jsou parametry uvedeny. Napadá mě snad leda řadit je abecedně (divné, co?). Pokud by dvě závislosti byly stejného typu, potom v pořadí source, destination apod.

Byť nám s tímto problémem může pomoci napovídání v IDE nebo automaticky generované kontejnery, nic to nemění na tom, že metoda s nejasnými parametry snižuje srozumitelnost kódu.

Jakožto líny člověk neoblibuji ani ty strojově se opakující přiřazování v těle konstruktoru. Jako zkratku lze použít:

class Foobar
{
    private $httpRequest, $router, $session;

    function __construct(HttpRequest $httpRequest, Router $router, Session $session)
    {
        list($this->httpRequest, $this->router, $this->session) = func_get_args();
    }

}

Ale pokud by byla poslední závislost nepovinná, mohlo by to skončit u Notice: Undefined offset.

Uvažuji nad sepsáním RFC pro PHP, aby bylo možné používat zápis:

class Foobar
{
    private $httpRequest, $router, $session;

    function __construct(HttpRequest $this->httpRequest, Router $this->router, Session $this->session)
    {
    }

}

Nicméně tohle jsou jen syntaktické libůstky oproti kruciálnímu problému s dědičností.

Co se stane, když vytvoříme potomka:

class Barbar extends Foobar
{
    private $logger;

    function __construct(HttpRequest $httpRequest, Router $router, Session $session, Logger $logger)
    {
        parent::__construct($httpRequest, $router, $session);
        $this->logger = $logger;
    }

}

Jak vidno, konstruktor potomka musí:

  • vyjmenovat závislosti rodiče
  • zavolat rodičovský konstruktor

To je v pořádku, závislosti rodiče jsou i jeho dědictvím. Jenže neexistuje mechanismus, kterým by se dalo volání rodičovského konstruktoru vynutit. Jednou z nejprotivnějších chyb se tak stane opomenutí volání parent::__construct. Takže předpoklad, že konstruktor už z principu vynucuje předání závislostí, je vlastně chybný. Konstruktor se dá snadno obejít.

Bez zajímavosti není, že zdáním je i neměnnost, protože nic nebrání zavolat na hotovém objektu $barbar->__construct(...) a protlačit mu jiné závislosti. Měl by tedy konstruktor testovat, zda není volán podruhé? Kašlete na to, konstruktor se prostě znovu volat nesmí. Otázka konvence.

Největší průšvih nastane ale ve chvíli, kdy provedu refactoring třídy Foobar, jehož důsledkem bude změna závislostí. Bude nutné přepsat konstruktory všech potomků. Jistě, je to logické, ale v praxi může jít o fatální zádrhel. Pokud například rodičem bude třída z frameworku (např. Presenter), jejíž potomky píše každý uživatel frameworku, fakticky se tak znemožní její vývoj, protože zásah do závislostí by byl kolosálním BC breakem.

Řada z výhod konstruktorové injektáže se rozplynula jak pára nad hrncem. Pokud se zdálo, že volání konstruktoru je vynuceno jazykem (silné a bezpečné), zatímco volání metod inject jen konvencí (opomenutelné), tak najednou se ukazuje, že to není zcela pravda.

Další možnosti

Možností, která částečně obchází problém konstruktoru a dědičnosti, je použití třídy FooDependencies zmíněné v článku Dependency Injection versus Service Locator:

class FoobarDependencies
{
    function __construct(HttpRequest $httpRequest, Router $router, Session $session)
}

class Foobar
{
    function __construct(FoobarDependencies $deps)
}

class Barbar extends Foobar
{
    function __construct(FoobarDependencies $deps, Logger $logger)
    {
        parent::__construct($deps);
        $this->logger = $logger;
    }
}

Když se změní závislosti rodičovské třídy Foobar, nemusí to nutně rozbít všechny potomky, protože se předávají v jedné proměnné. Běda ale, pokud ji předat zapomenou… Navíc tento způsob vyžaduje největší množství režijního kódu (dokonce celou režijní třídu).

Nebo lze závislosti rodičovské třídy Foobar předávat metodami a konstruktor uvolnit pro potomky. Rodič by se pak fakticky inicializoval až po volání těchto metod, takže konstruktor potomka by se volal nad neinicializovaným objektem. To není dobré.

A co obráceně, závislosti rodičovské třídy Foobar předávat konstruktorem a potomka metodami? To eliminuje všechny problémy, krom toho, že se těžko odhalí okamžik, kdy jsou nastaveny všechny závislosti (kvůli inicializaci objektu) a zda jsou vůbec nastaveny.

A co kdyby se všechny závislosti potomka nastavily jedinou metodou inject()? To by nejspíš vyřešilo všechny komplikace.

Nicméně stále jde jen o dvojstupňový případ rodič – potomek. Pro každého dalšího potomka by bylo třeba přijít s novou injektovací metodou a byl by problém zajistit, aby byly volány ve správném pořadí.

Dovedu si proto představit, že by vzniklo nové čisté řešení využívající nějaké PHP magie uvnitř třídy, která by ušetřila psaní režijního kódu, elegantně exponovala závislosti a předávala je do proměnných. Ty by mohly být označené třeba anotací @inject, nicméně šlo by o anotaci určenu pro tuto vnitřní implementaci, nikoliv o hint pro DI kontejner. Efekt by to mělo ve chvíli, kdyby se z toho stala obecněji uznávaná konvence, jinak to bude jen magie.

tl;dr

Předávání závislostí různými cestami má svá úskalí. Použití metod vyžaduje velké množství režijního kódu. Není od věci tyto metody pojmenovávat jiným prefixem, než obecné settery, kupříkladu lze použít inject. Poskytne to totiž důležitou informaci pro programátora, sekundárně ji může využít i DI kontejner.

Pokud nepoužíváte dědičnost, je zpravidla nejšikovnější závislosti předat skrze konstruktor a PHP by mohlo v příštích verzích syntaxi ještě o něco zjednodušit. Pokud ale do hry vstoupí dědičnost, je najednou všechno jinak. Ukazuje se, že dokonalý obecný mechanismus asi ani neexistuje. Možná by nebylo od věci zkusit nějaký, byť za využití PHP magie, vymyslet.


Všechny části:


SASS, LESS, Stylus nebo čisté CSS? (2)

Vyplatí se používat CSS preprocesory? A pokud ano, který zvolit? Pokračuji v rozboru tří nejznámějších konkurentů.

CSS preprocesor je nástroj, který vám ze zdrojového kódu zapsaného ve vlastní syntaxi vygeneruje CSS pro prohlížeč. Mezi nejznámější patří SASS, LESS a Stylus. Mají řešit praktické nedostatky samotného CSS.

předchozí části jsme si ukázali, jak preprocesory nainstalovat. Dnes se jim podívám na zoubek.

Syntaxe

Ač jednotlivé nástroje používají odlišnou syntax, všechny rozumí klasickému CSS. To je nesmírně důležité! Můžete kopírovat existující CSS fragmenty a budou fungovat. Ačkoliv…

SASS používá dvě různé syntaxe, jedna se nazývá SASS (stejně jako preprocesor), nepoužívá středníky ani složené závorky {} a místo toho odsazuje mezerami či tabulátory:

// SASS
#main
    color: blue
    font-size: 0.3em

Druhou syntaxí je SCSS, která vypadá jako klasické CSS a měla být i plně kompatibilní (budu ji pro SASS používat v dalších příkladech). Stejnou syntax má i LESS:

// SCSS, LESS a CSS
#main {
    color: blue;
    font-size: 0.3em;
}

Stylus také rozumí CSS syntaxi, nicméně upozorňuje, že nejde o 100% kompatibilitu, především proto, že hodně bazíruje na odsazování. V jeho syntaxi lze vynechat složené závorky, středníky a dokonce i dvojtečky:

// Stylus
#main
    color blue
    font-size 0.3em

Ale pozor! Důležité je, že tyto znaky můžeme vynechat. Pokud vám zápis připadá příliš hutný, klidně si dvojtečky doplňte. Nechcete odsazovat? Vražte tam složené závorky. Velmi podobně funguje NEON. V tomto se zásadně liší od úsporné SASS syntaxe, kde vynechání závorek a středníků je povinnost. Což ji činí z praktického hlediska nepoužitelnou a chápu, proč tvůrci o kompatibilitu s CSS usilovali, ale udělali to nešťastně zavedením další, nekompatibilní, ukecané syntaxe SCSS.

Když už se totiž rozhodnu preprocesor používat, ocením, když mi zjednoduší i syntaxi (alespoň nepovinné středníky), což bohužel umí jen Stylus. Jeho syntax je nejpružnější a nejúspornější.

V kontrastu přísnosti LESS a SASS je zajímavé, že všechny tři preprocesory podporují // řádkové komentáře.

Když udělám chybu

Pokud uděláte v dokumentu chibu, preprocesory vás na ni upozorní chybovou hláškou. A v tomto směru se jednotlivé nástroje zásadně liší. Pokud třeba zapomenu ve výše uvedeném kódu uzavírací }, oznámí SASS:

Syntax error: Invalid CSS after "font-size: 0.3em;": expected "}", was ""
        on line 4 of test.scss

Stylus:

      if (err) throw err;
                     ^
ParseError: stdin:4
   1| #main {
   2|   color: blue;
   3|   font-size: 0.3em;
 > 4|

unexpected "eos"

a LESS:

ParseError: missing closing `}` in test.less:9:17
8
9 unexpected "eos"undefined

Nutno dodat, že LESS měl šťastnější chvilku. Pokud bych zapomněl středník, skončí to u obvyklejšího

ParseError: Syntax Error on line 2 in test.less:2:1
1 #main {
2       color: blue
3       font-size: 0.3em

Výmluvné chybové hlášky jsou pro mě důležité a v tomto směru s přehledem vede SASS. Ačkoliv se to z ukázky tak nejeví, hodně se snaží i Stylus. Běžně však špatně spočítá číslo řádku a hlásí chybu o pár řádků výš, než skutečně je. LESS se nejčastěji zmůže na nicneříkající Syntax Error on line XY.

Občas jsem narážel na chyby v samotném preprocesoru. Pokud se mi je podařilo izolovat, snažil jsem se je nahlásit (LESS, Stylus), ale ne vždy to šlo. Zde se nejvíc projevila vyspělost jednotlivých nástrojů. Zatímco nejstarší SASS se mi z rovnováhy nepodařilo vyvést nikdy, druhé dva nástroje obsahují chyb bohužel požehnaně.

Co všechno umí?

Drtivou většinou užitečných vychytávek najdeme u všech tří nástrojů, byť třeba nemusí být uvedeny v dokumentaci. V množství asi vede SASS, ale takto se to hodnotit nedá. Třeba nested properties nepotřebuji, naopak property lookup a generátor data URI ve Stylusu vypadá užitečně. Tedy do chvíle, než zjistíte, že převádí buď všechny obrázky, nebo žádný.

Nechci sepisovat výčet jednotlivých vlastností ani srovnávat jejich zápis, to najdete v dokumentaci nebo ve srovnávacím článku na NetTuts. Zaměřím se místo toho na fundamentální rozdíly a největší z nich se týkají tzv. mixinů.

Mixiny

Mixiny jsou z mého pohledu nejdůležitější vychytávkou preprocesorů, protože mění způsob, jak píšeme HTML kód. Vysvětlím. Asi nejznámější „mixin“ je tzv. clearfix, která se stará o to, aby z bloku nevytékaly plovoucí prvky. Obvykle vypadá nějak takto:

.clearfix:after {
    clear: both;
    content: ".";
    display: block;
    height: 0;
    visibility: hidden;
    font-size: 0;
}

(Poznámka: existují jednodušší i složitější varianty clearfixu. Kupodivu mně vždycky fungovalo nastavit bloku overflow: auto a clearfix jsem nepotřeboval. Ale v tuto chvíli jde jen o příklad.).

Clearfix pak aktivujeme v HTML:

<div id="content" class="clearfix">
    <div style="float: left;">....</div>
    ...
</div>

Ale to neděláš dobře, Jaromíre. Clearfix by se měl aplikovat na straně stylů, přímo pro #content, nikoliv v HTML. Jenže komu by se chtělo kazit si krásný stylopis kopírováním ošklivého hacku? Mnoho kodérů raději sáhne do HTML.

S preprocesory však netřeba nic kopírovat a vystačíme si s jedinou instrukcí. Nejprve si clearfix uložíme jako tzv. mixin a poté ho v definici #content zavoláme:

SASS:

// definice mixinu (zkráceno)
@mixin clearfix {
    &:after { clear: both; ... }
}

#content {
    @include clearfix; // vložení mixinu
}

LESS:

.clearfix() {
    &:after { clear: both; ... }
}

#content {
    .clearfix;
}

Stylus:

clearfix()
    &:after { clear: both; ... }

#content
    clearfix()

Zmizí tak nutkání zasahovat do zdrojového kódu. A to je velmi důležité.

Jak vidno, syntaxe SASS je opět nejukecanější. Za zmínku stojí, že LESS umožňuje volat jako mixin i jakoukoliv třídu nebo ID.

Parametrické mixiny

Ještě zajímavější je to ve chvíli, kdy mixinům předáme parametry:

SASS:

@mixin border-radius($arg) {
    -webkit-border-radius: $arg;
    -moz-border-radius: $arg;
    border-radius: $arg;
}

#content {
    @include border-radius(2px);
}

LESS:

.border-radius(@arg) {
    -webkit-border-radius: @arg;
    -moz-border-radius: @arg;
    border-radius: @arg;
}

#content {
    .border-radius(2px);
}

Stylus:

border-radius(arg)
    -webkit-border-radius: arg
    -moz-border-radius: arg
    border-radius: arg


#content
    border-radius(2px)

Ve Stylusu můžeme použít i zapis s dvojtečkou:

#content
    border-radius: 2px

Což je velmi zajímavé! Když totiž později vendor prefix odstraníme, nebo když naopak zjistíme, že určité vlastnosti potřebujeme vendorovou variantu dodat, nemusíme změnit ani čárku v samotném stylu. Wow.

Jenže… co když mixin zavoláme s více parametry? Například uvedeme border-radius: 2px 3px. Překvapivě Stylus bude 3px v tichosti ignorovat. Proč tomu tak je?

V CSS existují dva způsoby, jak uvést více argumentů, lišící se v oddělovači:

  • oddělené mezerama: border: 1px solid black
  • oddělené čárkama: rgb(10, 20, 30)

Totéž se rozlišuje i v preprocesorech. Ve Stylusu platí, že volání mixinu border-radius: 2px 3px je ekvivalentní k border-radius(2px, 3px) (a dává mi to smysl), obojí představuje předávní dvou argumentů. Naopak border-radius(2px 3px) bez čárky předává argument jeden. Náš mixin očekává pouze jeden parametr a druhý tiše bez jakéhokoliv varování zahodil. Oprava spočívá v použití užitečného klíčového slova arguments namísto předaného argumentu arg.

SASS a LESS naopak předání více argumentů, než je mixinem očekáváno, vnímají jako fatální chybu. V případě SASS to v důsledku vede k podivným blokům $shadow-1, …, $shadow-10 patrným v galerii Compass, zatímco LESS se s problémem snaží vypořádat konstrukcí when(). Nic z toho mi nepřipadá ideální.

Pokračování zase příště.


SASS, LESS, Stylus nebo čisté CSS? (1)

Už pár let jsem si pořádně nezakódoval a začalo mi to chybět. Zachtělo se mi udělat stránky podle nejnovějších trendů. Responsivní design okořeněný CSS preprocesory. Ale váhal jsem: jsou preprocesory víc, než jen chvilková móda?

CSS preprocesor je nástroj, který vám ze zdrojového kódu zapsaného ve vlastní syntaxi vygeneruje CSS pro prohlížeč. Mezi nejznámější patří SASS, LESS a Stylus.

Faktem je, že jakmile začne stylopis kynout do větších rozměrů, řada věcí se řeší dosti nepohodlně. Je třeba vynaložit úsilí, aby zůstal čitelný a srozumitelný. Aby se z něj nestal write-only soubor plný magický konstant a hacků. Spoustu těchto nešvarů preprocesory řeší, nejvíc se těším na vnořené definice, matematické výrazy, mixiny a proměnné.

Vlastně je smutné, že preprocesory musely vzniknout. Ukazuje to na zanedbanost vývoje CSS. Na druhou stranu, může z nich i těžit. Preprocesory jsou mladé projekty procházející bouřlivým vývojem, reagují na potřeby uživatelů a lze u nich, na rozdíl od standardu, tolerovat i případné nekompatibilní změny. Ve finále tak mohou ukázat směr, kterým se má vydat příští CSS.

Pokud se kódováním webů bavíte řadu let, máte vybudované konvence pomáhající nedostatky obcházet. Preprocesory pak nemusí být úplně samozřejmou volbou. Nicméně dnes je běžné používat různé CSS generátory a preprocesor nabízí čistější cestu, než copy&pastovat vygenerovaný kód.

Rád zkouším nové věci, proto jsem dal preprocesorům šanci. Který ale zvolit? Nejlepší je si všechny osahat.

Instalace

Začneme tedy rovnou instalací. Na webu SASS i LESS rychle najdete vyčerpávající postup, jak knihovny získat. SASS je napsaný v Ruby, LESS v Node.js, takže prvním krokem bude instalace překladače, což by neměl být v žádném operačním systému problém. Preprocesor pak nainstalujete příkazem:

gem install sass

resp.

npm install less

Velmi snadné, PHP může závidět.

Naopak web Stylusu selhává, snaží se mást odkazy na Github, zatímco informace, jak ho instalovat, je důmyslně skrytá kdesi vespod úvodní stránky. Vězte tedy, že Stylus je také napsán v Node.js a nainstaluje se obdobně příkazem npm install stylus.

Pozor na jednu věc, npm instaluje balíček do aktuálního adresáře, takže abyste mohli preprocesory pohodlně spouštět z příkazové řádky, musíte si cestu k lessc.cmd a stylus.cmd přidat do proměnné PATH. Ve Windows se to dělá sparťansky, takže spíš oceníte možnost nainstalovat balíčky do globálního adresáře (pomocí přepínače -g, tj. npm install -g stylus), ke kterému už cestu zaregistroval při instalaci překladač.

Příkazová řádka pro SASS a Stylus nabízí spoustu voleb, LESS umí jen konvertovat vstupní soubor do výstupního CSS. Zmátlo mě, že SASS i Stylus zavolané bez parametrů se nijak neohlásí a očekávají vstup z klávesnice. Zavolejte je tedy s parametrem -h a vypíše se nápověda všech voleb.

Vývoj a deployment

Preprocesory vyžadují, aby se mezi úpravou stylopisu a zobrazením v prohlížeči udělat jeden krok navíc: kompilace do CSS.

Tento krůček může mnohé odradit. Pokud jste zvyklí rovnou editovat CSS soubory na FTP serveru, nad preprocesorem vůbec neuvažujte. Existují sice možnosti, jak třeba z LESS generovat CSS za běhu přímo v prohlížeči, ale rozhodně nejsou určeny pro produkční nasazení.

Pokud máte deployment automatizovaný, stačí do procesu zařadit kompilaci voláním příkazové řádky a je vystaráno.

Jak řešit onen krok navíc během vývoje? Kodéra rozhodně blbnutí s příkazovou řádkou nezajímá a chce rovnou psát stylopis.

Jak jsem zmínil, LESS umí překládat stylopisy v prohlížeči, stačí tedy zalinkovat soubor less.js a můžete rovnou připojovat soubory ve formátu LESS (povšimněte hodnoty atributu rel):

<link rel="stylesheet/less" type="text/css" href="styles.less">
<script src="less.js" type="text/javascript"></script>

SASS a Stylus zase nabízejí sledovací režim, ve kterém monitorují adresář se styly ve svém formátu a při každé změně souboru je ihned překládají do CSS.

// překlad souborů z adresáře /css.sass do /css (včetně podadresářů)
sass --watch css.sass:css

// překlad souborů z adresáře /css.stylus do /css
stylus --watch css.stylus --out css

Do vygenerovaného CSS lze pomocí přepínače --line-numbers přidat pro lepší orientaci komentáře s číslem řádku zdrojového souboru. Pokud vyvíjíte ve Firefoxu, ještě užitečnější je nainstalovat FireStylus a kompilovat s přepínačem --firebug. V záložce HTML by se pak měly objevovat odkazy přímo na zdrojový soubor. Píšu měly, protože mi to nefunguje.

Všechny tři preprocesory jsou seřazeny na startovní čáře. Který z nich běží nejlépe? Pokračování příště.

(Doporučuji aktuálnější průvodce CSS preprocesory od Martina Michálka, první a druhý díl.)


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.