Klávesové zkratky na tomto webu - rozšířené Na obsah stránky

PHAR - PHP v kompaktním balení

Je tomu právě tři a půl roku, kdy jsem distribuci Texy doplnil o tzv. kompaktní verzi. Zjednodušeně se dá říci, že jde o skript vzniklý spojením všech souborů tvořících knihovnu do jednoho jediného a vynecháním komentářů a nadbytečných mezer. Výsledkem je funkčně identický kód, jen s krapet hustší konzistencí. (Prostě je hustéééj. Jak Salko!)

Záměrem bylo zjednodušit nahrávání knihovny na server – uploadovat desítku malých souborů přes FTP bývá značně pomalejší, než nahrát soubor jeden. Jako vedlejší efekt se ukázalo, že jednosouborová knihovna znatelně zvyšuje výkon aplikace – klidně o 30 %. Používání kompaktních verzí se ujalo a od té doby většina bugreportů hlásila chybu na řádce 1 :-)

S příchodem jmenných prostoru v PHP 5.3 se generování kompaktních verzí poněkud zkomplikovalo – zatím totiž platí, že jeden soubor může obsahovat jen jeden jmenný prostor. To znamená, že „jednosouborová“ verze bude mít přinejmenším tolik souborů, kolik je v knihovně jmenných prostorů, plus případné další soubory řešící problémy se vzájemnou závislostí mezi prostory.

Zároveň však PHP 5.3 přichází s novinkou, která si klade za cíl vnést do kompaktních verzí nový svěží vítr: PHAR – PHP archive. Ostatně myslím, že promluvím za nás všechny, když řeknu: nemusí pršet, hlavně když kape!

Jak takový PHAR balíček vytvořit? Nejprve je nutné v PHP.INI zapnout extension=php_phar.dll a nastavit direktivu phar.readonly = Off. Následující kód zkonvertuje celý Nette Framework do souboru nette.phar

$phar = new Phar('nette.phar');

$phar->buildFromDirectory('libs/Nette'); // enter here path to Nette

$phar->setStub("<?php
Phar::mapPhar('nette.phar');
require 'phar://nette.phar/loader.php';
__HALT_COMPILER();");

$phar->compressFiles(Phar::GZ); // or Phar::BZ2

Nejprve se vytvoří nekomprimovaný archiv ze všech souborů v určeném adresáři a jeho podadresářích – jde o obdobu TAR archivu. Následně se definuje tzv. stub neboli zaváděcí program. Jeho úkolem bude načíst soubor loader.php, který je součástí Nette Frameworku a nalézá se vlastně uvnitř PHAR archivu. Stub musí být ukončen zvoláním __HALT_COMPILER(). Poté je možno archiv zkomprimovat.

Příklad použití:

require 'nette.phar';

echo Html::el('strong')->setText('It works!');

Vypadá to báječně, že? Ovšem podívejme se na věc podrobněji. PHP skripty nejsou v archivu nijak minimalizované, nejsou z nich odstraněny zbytečné komentáře ani mezery. To je potřeba udělat ručně. Takže zatímco komprimovaný nette.phar má nějakých 180 kB, stejný archiv vygenerovaný z kompaktní verze Nette (tj. místo adresáře libs/Nette použijeme libs/Nette.compact) váží pouhých 50 kB. Nicméně pokud si uvědomíme, že

  • minimalizace kódu vede k rychlejšímu parsování (závislost bude dost možná lineární)
  • dekomprimace naopak nutně zpomaluje

dojdeme k závěru, že ideální bude generovat PHAR rovnou z kompaktní verze a ukládat jej nekomprimovaný. Nojo – ale proč teda rovnou nezůstat u kompaktní verze? Bez PHAR režie navíc?

Důvod existuje. Kompaktní verze se vždy načte celá, zatímco PHAR lze parsovat a kompilovat po částech. Takže Zend Framework bude vhodnější zabalit do PHARu, u dibi nebo Texy si můžeme dovolit plný výkon kompaktní verze.

clock před 6 dny pencil PHP comments Komentáře: 12


Singleton Sofie S.

Slíbil jsem přijít se silným lidským příběhem. Příběhem programátorky, která stojí před těžkou životní volbou. Její jméno je Sofie.

Jak už jste pochopili z druhé věty, Sofie nebyla právě pohledná žena. Zpoza prořídlých neupravených kadeří na vás přes velké umaštěné brýle hleděly dioptriemi zvětšená drobná očka, kostěné obroučky marně skrývaly uhří farmu táhnoucí se podél líček až ke knírku. V redakci, kde pracovala, platila za velmi zdatnou programátorku. Na svůj redakční systém byla náležitě pyšná, snad ještě víc, než na titul Miss Second Life 2007. Pýcha byla oprávněná, kód systému byl tak křišťálově čistý, že ani křišťál křišťálově čistější být nemůže.

Ačkoliv měla Sofie z návrhových vzorů velmi ráda právě singleton, jednalo se spíš o spřízněnost duševní – v kódu jej používala umírněně, jen tam, kde se skutečně hodil. Například pro zapouzdření HTTP požadavku:

/**
 * @copyright Sofie S., 2006
 */
class HttpRequest
{
    /********************* singleton *******************/

    private static $instance;

    private function __construct()
    {}

    public static function getInstance()
    {
        if (self::$instance === NULL) {
            self::$instance = new self;
        }
        return self::$instance;
    }

    private function __clone()
    {}

    private function __wakeup()
    {}

    /********************* HTTP request encapsulation *******************/

    public function getRemoteAddress()
    {
        return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : NULL;
    }

    ...
}

// užití
if (HttpRequest::getInstance()->isPost()) {
    echo 'Hey mister POSTman!';
}

To pro představu snad stačí. Víc kódu ani ukázat nemůžu, protože nemám Sofiin souhlas a mazat kopyrajty si mi nechce. Ale určitě se shodneme na tom, že zapouzdření HTTP požadavku do formy singletonu je perfektní volba:

  • HTTP požadavek může být jen jeden (a právě jeden)
  • je potřeba jeho globální dostupnost
  • hezky líně se inicializuje až při prvním použití

Klíčový je samozřejmě první bod. Mohlo by se stát, že by v budoucnu bylo potřeba mít dva HTTP požadavky? To bezpochyby ne, odpověděla si Sofie už v době návrhu systému a volba singletonu byla jasná věc.

Nemá smysl zastírat, že se Sofii líbil jeden redaktor z vedlejší kanceláře. Věděli to všichni. Ona z něj byla doslova hotová! Stačilo, aby prošel kolem jejího počítače a rvala si rodidla. Proto když jednoho pondělního rána našla na stole vzkaz, že s ní chce o něčem mluvit, málem štěstím omdlela.

Řeč se týkala redakčního systému. Eman nešetřil chválou a dokonce prohlásil, že Sofie je jejich poklad a co by si bez ní počal. Pak se jí, snad omylem, letmo dotkl ramene. Sofie pocítila v podbřišku slastné zachvění. „Je tu něco, co jsem ti chtěl už dávno říct,“ pohlédl jí Eman do očí, „když mám dlouho otevřený článek a pak kliknu na sejf, tak se mi objeví: byl jste automaticky odhlášen, bla bla bla, a celý článek je v řiti. Nemůžeš s tím něco udělat?“

Nebylo na světě nic, co by Sofie pro Emana neudělala. Ihned se pustila do práce.

V podstatě šlo o banální úkol. Dokonce tuhle slyšela, že stejný problém dokázali po dvou letech vyřešit borci z Jyxo na svém serveru blog.cz, to by bylo, aby si s tím ona během půl hodinky neporadila! Princip je jednoduchý: v okamžiku, kdy se detekuje vypršení časového limitu a uživatel se automaticky odhlásí, tak se objekt s aktuálním HTTP požadavkem uloží do session. A pak stačí jen upravit přihlašovací rutinu, aby po úspěšném přihlášení, zjistí-li v session uložený tento objekt, aby jej nastavila jako aktuální. Je to otázka pár řádek kódu a za dvacet minut už bude sedět u Emana a předvádět mu, jak to báječně funguje. Dají si společně šálek kávy, plánovala si. A možná … možná ji bude chtít pozvat na skleničku, ačkoliv ona sklo nerada. Ale Emanovi neumí říci ne.

Na kávu ten den nedošlo. Zrada přišla z míst, odkud je Sofie nečekala. Zradil ji singleton. Sofie musela povolit serializaci objektu HttpRequest, musela vyřešit prohození těchto objektů v metodě getInstance, ale především se musela smířit s faktem, že na ten krátký okamžik, kdy se uložený požadavek obnovil ze session, tak na tu chvíli existovaly požadavky dva. I singleton tvořil pár. Jen ona zůstala sama.

clock před 15 dny pencil PHP comments Komentáře: 10


Je singleton zlo?

Singleton je jedním z nejpopulárnějších návrhových vzorů. Jeho úkolem je zajistit existenci pouze jediné instance určité třídy a zároveň poskytnout globální přístup k ní. Pro úplnost malý příklad:

class Database
{
    private static $instance;

    private function __construct()
    {}

    public static function getInstance()
    {
        if (self::$instance === NULL) {
            self::$instance = new self;
        }
        return self::$instance;
    }

    ...
}

// singleton je globálně dostupný
$result = Database::getInstance()->query('...');

Typickými rysy jsou:

  • privátní konstruktor, znemožňující vytvoření instance mimo třídu
  • statická vlastnost $instance, kde je unikátní instance uložená
  • statická metoda getInstance(), která zpřístupní instanci a při prvním volání ji vytvoří (lazy loading)

Jednoduchý a snadno pochopitelný kód, který řeší dva problémy objektového programování. Přesto v dibi nebo Nette Framework žádné singletony nenajdete. Proč?

Unikátnost jen zdánlivá

Podívejme se na kód pozorněji – skutečně ručí za existenci pouze jedné instance? Obávám se, že nikoliv:

$dolly = clone Database::getInstance();

// nebo
$dolly = unserialize(serialize(Database::getInstance()));

// nebo
class Dolly extends Database {}
$dolly = Dolly::getInstance();

Proti tomu existuje obrana:

final public static function getInstance()
{
    // finální getInstance
}

final public function __clone()
{
    throw new Exception('Clone is not allowed');
}

final public function __wakeup()
{
    throw new Exception('Unserialization is not allowed');
}

Jednoduchost implementace singletonu je ta tam. Ba co hůř – s každým dalším singletonem se nám bude opakovat kusanec stejného kódu. Také třída najednou plní dva zcela odlišné úkoly: kromě svého původního úkolu se stará o to být dost single. Obojí je varovný signál, že něco není v pořádku a kód by si zasloužil refaktoring. Vydržte, za chvíli se k tomu vrátím.

Globální = ošklivé?

Singletony poskytují globální přístupový bod k objektům. Není nutné si odkaz na ně neustále předávat. Zlí jazykové však tvrdí, že taková technika se nijak neliší od používání globálních proměnných a ty jsou přeci čiré zlo.

(Pokud metoda pracuje s objektem, který jí byl nějak explicitně předán, parametrem nebo jde o proměnnou objektu, říkám tomu „drátové spojení“. Pokud pracuje s objektem, který si získá přes globální bod (např. skrz singleton), říkám tomu „bezdrátové spojení“. Docela fajn analogie, ne?)

Zlí jazykové se v jedné věci mýlí – na „globálním“ není a priori nic špatného. Stačí si uvědomit, že název každé třídy a metody není nic jiného, než globální identifikátor. Mezi bezproblémovou konstrukcí $obj = new MyClass a kritizovanou $obj = MyClass::getInstance() není ve skutečnosti zásadní rozdíl. Tím méně u dynamických jazyků jako je PHP, kde lze psát $obj = $class::getInstance().

Co ale může způsobit bolení hlavy, to jsou

  1. skryté závislosti na globálních proměnných
  2. nečekané používání „bezdrátových spojení“, které z API tříd nevyčtete (viz Singletons are Pathological Liars)

První bod se dá eliminovat, pokud se nebudou singletony chovat jako globální proměnné, ale spíš jako globální funkce či služby. Jak tomu rozumět? Vezměte si třeba google.com – hezký příklad singletonu jakožto globální služby. Existuje jedna instance (fyzická serverová farma kdesi v USA) globálně dostupná přes identifikátor www.google.com. (Dokonce ani clone www.google.com nefunguje, jak zjistil Microsoft, maj to holt kluci vychytaný.) Důležité je, že tato služba nemá skryté závislosti typické pro globální proměnné – vrací odpovědi bez neočekávatelné souvislosti s tím, co před chvíli hledal někdo úplně jiný. Naopak nenápadná funkce strtok trpí vážnou závislostí na globální proměnné a její používání může vést k velmi těžko odhalitelným chybám. Jinými slovy – problém není v „globálnosti“, ale v návrhu.

Také druhý bod je čistě záležitostí návrhu kódu. Není chybou použít „bezdrátové spojení“ a přistoupit ke globální službě, chybou je to dělat proti očekávání čili skrytě. Programátor by měl přesně vědět, jaký objekt využívá které třídy. Poměrně čistým řešením je mít v objektu proměnnou odkazující na objekt služby, která se inicializuje na globální službu, pokud programátor nerozhodne jinak (technika convention over configuration).

Unikátnost může být na škodu

K singletonům se váže problém, na který narazíme nejpozději při testování kódu. A tím je potřeba podstrčit jiný, testovací objekt. Vraťme se ke Google jakožto ukázkovému singletonu. Chceme otestovat aplikaci, která jej využívá, jenže po pár stech testech začne Google protestovat We're sorry… a jsme kde? A jsme někde. Řešením je pod identifikátor www.google.com podstrčit fiktivní (mock) službu. Potřebujeme upravit soubor hosts – jenže (zpět z analogie do světa OOP) jak toho dosáhnout u singletonů?

Jednou z možností je implementovat statickou metodu setInstance($mockObj). Ale ouha! Copak asi chcete slečně metodě předat, když žádná jiná instance, než ona jedna jediná, neexistuje?

Jakýkoliv pokus odpovědět na tuto otázku povede nevyhnutelně k rozpadu všeho, co singleton dělá singletonem.

Pokud odstraníme restrikce na existenci jen jedné instance, přestane být singleton single a my řešíme pouze potřebu globálního úložiště. Pak je nabíledni otázka, proč v kódu stále opakovat stejnou metodu getInstance() a nepřesunout ji do extra třídy, do nějakého globálního registru?

Anebo ponecháme restrikce, pouze identifikátor v podobě třídy nahradíme za interface (DatabaseIDatabase), čímž se vynoří problém nemožnosti implementovat IDatabase::getInstance() a řešením je opět globální registr.

O pár odstavců výše jsem sliboval vrátit se k otázce opakujícího se kódu ve všech singletonech a možnému refaktoringu. Jak vidíte, problém se mezitím vyřešil sám. Singleton zemřel.

clock před 17 dny pencil PHP comments Komentáře: 41


Řetězení výjimek v PHP

Dobře utajenou novinkou PHP 5.3 je řetězení výjimek (exception chaining). Tuto vlastnost můžete znát třeba z Javy (caused exception) nebo ASP.NET (inner exception). O co jde? Každá vyhozená výjimka se může odkazovat na jinou výjimku, která její vznik zapříčinila. Jinými slovy, je možné zachycenou výjimku zabalit do obecnější výjimky.

function loadFile($file)
{
    if (!is_file($file)) {
        throw new FileNotFoundException("File '$file' not found.");
    }
    ...
}


function getConfig()
{
    try {
        $s = loadFile('config.ini');
        ...
    } catch (FileNotFoundException $e) {
        // obalíme výjimku
        throw new ConfigException("Missing configuration.", 0, $e);
    }

}


try {
    $config = getConfig();
} catch (ConfigException $e) {
    echo $e;
}

Jak vidíte, konstruktor třídy Exception se dočkal rozšíření o třetí nepovinný parametr, kterým lze zřetězenou výjimku nastavit. Získat ji můžeme metodou getPrevious(). Zřetězená výjimka se zahrne i do textového výpisu:

exception 'FileNotFoundException' with message 'File 'config.ini' not found.' in demo.php:8
Stack trace:
#0 demo.php(17): loadFile('config.ini')
#1 demo.php(28): getConfig()
#2 {main}

Next exception 'ConfigException' with message 'Missing configuration.' in demo.php:21
Stack trace:
#0 demo.php(28): getConfig()
#1 {main}

Všimněte si, že výjimky se poněkud nešťastně vypisují v pořadí, v jakém vznikly. Očekával bych pořadí opačné.

clock před měsícem pencil PHP comments Přidej komentář!


Jak se liší MVC a MVP?

Takže, jaký je vlastně rozdíl mezi MVC a MVP? Zkusíme použít WinMerge:

(viz Phil Haack: Everything You Wanted To Know About MVC and MVP But Were Afraid To Ask)

clock před měsícem pencil PHP comments Komentáře: 3


TOPlist

phpFashion © 2004, 2008 David Grudlo webu

Jakékoliv užití obsahu, včetně převzetí článků nebo jejich částí, je bez předchozího písemného svolení autora zakázáno.

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.

Tipy: Nafukovací čluny, lodě, kajaky, rafty | Navigace GPS | Spolehlivý obchod | Pevné zdraví, léky, afrodiziaka, fitness, lékárna | Parfémy | Dieta, hubnutí