phpFashion

Na navigaci | Klávesové zkratky

Který framework má nejlepší dokumentaci?

Zajímalo mě, který PHP framework má nejlepší dokumentaci. A jak si v žebříčku stojí Nette. Jenže jak to zjistit?

Všichni víme, že nejhorší je žádná dokumentace. Pak následuje nedostatečná dokumentace. Opakem je obsáhlá dokumentace. Tedy zdá se, že podstatným vodítkem je samotný objem dokumentace. Pochopitelně obrovskou roli hraje i srozumitelnost a aktuálnost, dojem dělá čtivost a bezchybnost. Tyto faktory se velmi těžko měří. Nicméně sám vím, kolik částí dokumentace Nette jsem mnohokrát přepsal, aby byly jasné, kolik oprav jsem mergoval, a předpokládám, že se tak děje u každého letitého frameworku. Že tedy postupně všechny dokumentace konvergují k podobné kvalitě. Tudíž si jako vodítko dovolím brát čistě objem dat.

Pochopitelně se objem dokumentace musí dát do poměru s velikostí té které knihovny. Některé jsou i řádově větší než jiné a pak by měly mít i řádově větší dokumentaci. Nejjednodušší je velikost knihovny stanovit podle objemu PHP kódu. S normalizovaným bílým místem, bez komentářů.

Vytvořil jsem graf poměru anglické dokumentace ku kódu u známých frameworků CakePHP (3.8), CodeIgniter (3.1), Laravel (5.8), Nette (3.0), Symfony (4.4), YII (2.0) a Zend Framework (2.x):

Jak z grafu vidíte, obsáhlost dokumentace vůči kódu je u všech frameworků velmi podobná.

Vyčnívá CodeIgniter, což je dáno i tím, že jde o opravdu malý framework. Smekám před CakePHP a YII, které se snaží udržovat dokumentaci v celé řadě dalších jazyků. Obsáhlost dokumentace Nette je takřka stejná, jako v případě Symfony nebo Zend Frameworku. Nette je jediný framework, který má 1:1 překlad i v naší mateřštině.

Doslov: smyslem grafu není ukázat, že jeden sloupeček je o trošku vyšší nebo nižší než druhý. Při jiné normalizaci by se poměry změnily, metrika je primitivní. Smyslem je vyvrátit mýtus, že Nette má chabou dokumentaci. Samozřejmě, že dokumentace by mohla být (a bude!) mnohem obsáhlejší, určitě tam spousta věcí chybí, ale úplně stejně je tomu u všech ostatních frameworků. Když někde čtu, jak je dokumentace špatná, a pisatele se zeptám, co přesně mu chybělo, abych to mohl doplnit, obvykle z odpovědi uteče, že mnoho let dokumentaci neviděl a vlastně neví. Naopak při osobním setkání nebo emailem mi řada programátorů dokumentaci Nette pochválila, že jim připadá skvělá. Tento graf jsem vytvořil hlavně pro sebe, abych získal náhled na skutečnost.

před 7 dny v rubrice PHP


Typed properties v PHP 7.4 jsou cool, ale ne jak si myslíte 😏

Po dvou verzích PHP, které nepřinesly nic moc zajímavého, se blíží verze, pro kterou bude mít opět smysl aktualizovat knihovny. Jde o PHP 7.4 a hlavním tahákem jsou typed properties, které uzavírají mnohaletý posun ke striktně typovanému jazyku, což PHP zvýhodňuje oproti jiným webovým jazykům.

Ve zkratce, tahle novinka vám umožní deklarovat typy přímo u proměnných třídy:

class Config
{
    public string $dsn;
    public ?string $user;
    public ?string $password;
    public bool $debugger = true;
}

Příklad použití najdete i v dokumentaci Nette Schema, které je na ně už dnes připravené.

Je potřeba říct, že pokud jste si navykli používat privátní proměnné a přistupovat k nim přes typehintované metody (což je správně), tak vlastně o žádnou killer feature nejde. Druhotná kontrola typů je zbytečná a vlastně jen zpomaluje kód. Diametrálně jiná situace se týká public/protected proměnných, kde dosud neexistoval žádný způsob, jak mít jejich hodnotu (a dokonce existenci) pod kontrolou. Až dosud.

Což nevyhnutelně povede k otázce:

Je nutné dál psát settery a gettery?

Sice veškerý boilerplate kód nám dnes na kliknutí generují editory, ale určitě vypadá hezky, když tohle:

class Circle
{
    private $radius;

    function setRadius(float $val)
    {
        $this->radius = $val;
    }

    function getRadius(): float
    {
        return $this->radius;
    }
}

nahradíte za:

class Circle
{
    public float $radius;
}

Nehledě na to, že i užití objektu je stručnější $circle->radius = $x vs $circle->setRadius($x).

Problém ale je, že velkou spoustu setterů a getterů nelze jednoduše nahradit. Třeba zrovna v uvedeném příkladu by se hodilo ještě ověřit, že poloměr není záporné číslo:

function setRadius(float $val)
{
    if ($val < 0) {
        throw new InvalidArgumentException;
    }
    $this->radius = $val;
}

A v ten moment už nelze kód zredukovat do veřejné proměnné.

Jindy zase chceme, aby jednou nastavená hodnota byla neměnná, což nelze u public proměnné zajistit.

Nebo vůbec nechceme dávat k dispozici getter, protože nepatří do veřejného API třídy.

Anebo chceme mít setter či getter součástí rozhraní.

Atd, atd.

Zkrátka někdy bude možné použít typované veřejné proměnné místo metod, jindy ne, rozdíl bude dost často otázkou vnitřní implementace třídy a pro uživatele neprůhledný. Což je cesta v nekonzistentnímu API, kdy jednou se používá proměnná, jindy metoda a uživatel v tom nevidí logiku. (Podobně jako třeba metoda PDOStatement::errorInfo() vs. proměnná PDOException::$errorInfo).

Prohlubování nekonzistence ale nechceš. Raději konzistentní setrvání u metod, privátních proměnných a všeho toho boilerplate kódu. A pro privátní proměnné, jak jsem zmiňoval v úvodu, je přínos typehintů sporný. Nebo ne?

V čem je tedy výhoda?

Vlastně je výhod dost, i když v jiných oblastech. Typované proměnné budou užitečné pro kompilátor kvůli optimalizacím, pro práci s reflexí nebo nástroje analyzující kód. Důležité budou v šedé zóně protected proměnných. Umožňují zapsat prostředky jazyka to, co se dosud obcházelo komentářem. A navíc přinášejí do jazyka nový příznak neinicializovaného stavu, jakousi obdobu undefined z JavaScriptu.


Texy 3.0: do dokonalosti se nezasahuje

Něco jako když zahlédnete plakát ke koncertu kapely, kterou si vybavujete z mládí. Oni stále hrají? Nebo se dali dohromady po letech, protože potřebují peníze? Něco vyždímat na strunách nostalgie?

Texy je můj první open source projekt. Začal jsem ho psát před patnácti lety. Texy přežilo několik verzovacích systémů. Několik webových služeb hostujících repositáře. Několik kódování řetězců. Několik značkovacích jazyků pro tvorbu webových stránek. Několik mých životních vztahů. Několik měst, ve kterých jsem bydlel.

Texy je tady stále, protože neexistuje nic lepšího.

Takže jej patnáct let udržuji up-to-date. Začínali jsme v PHP 4, což byl ten nejhorší programovací jazyk na světě a tedy výzva, pak s úlevou přešli na PHP 5, o pár let později se ukryli do jmenného prostoru (Texy::Parser místo TexyParser, wow), sledovali, jak PHP přestávalo být nejhorším jazykem na světě, což otrávilo spoustu programátorů, kteří si našli náhradu v JavaScriptu, poté bůh stvořil PHP 7 a s ním typehinty (Texy::process(string $text): string megawow) a do módy přišla striktnost declare(strict_types=1) a tu my ctíme.

A proto je tu Texy 3.0.. Jde o úplně totéž, jako předchozí verze, ale má všechny vychytávky PHP 7.1. Je to úplně totéž proto, že do dokonalých věcí se nezasahuje.

Texy tu bylo, když jste se narodili. Programátorsky. Texy jednou bude formátovat váš epitaf. A mezi v a pokoji vloží nedělitelnou mezeru.



Co se chystá v Nette?

Co přinesou příští verze Nette Frameworku a jaký je plán pro další vývoj?

Nette je tvořeno řadou knihoven, z nichž některé patří mezi světovou špičku: Latte je nejbezpečnější šablonovací systém, Tracy je mnohými považován za nejpřívětivější debugovací nástroj, Dependency Injection Container patří mezi ty nejpohodlněji použitelné. Spousta konceptů vznikla šťastnou rukou a fungují v prakticky nezměněné podobě už 10 let, například formuláře nebo komponentový systém presenterů.

Nicméně ve všech oblastech je hodně příležitostí co vylepšovat a inovovat. A nápadů je spousta.

Brzký příchod Nette 3

Jelikož podpora Nette 2.4 podle harmonogramu skončila v červnu 2018, tedy po dvou letech od jeho vydání, je hlavní prioritou brzký příchod nové verze Nette 3. Ta bude určená pro PHP 7.1 a bude využívat jeho zásadních novinek, jako:

  • poběží ve striktním režimu
  • kód bude používat skalární typehinty
  • metody budou mít návratové typehinty

Zároveň je záměr připravit:

  • nástroj pro automatizovanou aktualizaci kódu pro novou verzi frameworku (jako tomu bývalo u Nette 0.9 a 2.0)
  • aktualizovat dokumentaci včetně příkladů
  • vygenerovat API dokumentaci na webu https://api.nette.org (v současnosti chybí generátor dokumentace)
  • zajistit kompatibilitu s připravovaným PHP 7.3

Do Nette 3 se mohou zahrnout i některé z plánovaných featur uvedených níže, ale nic, co by oddalovalo vydání. Stabilní verze následovaná po testovacích RC by mohla vyjít v rozmezí října až listopadu 2018 (viz konec článku).

Nette 4

Ihned po vydání Nette 3 by začal vývoj Nette 4. Hlavní novinkou by byla změna vnitřní architektury na tzv. middleware. Komponenta pro zpracování a sanitizaci HTTP požadavku, router, PresenterFactory i samotný presenter by byly vrstvy middleware, mezi které bude možné snadno včlenit další vrstvy, nebo tyhle existující nahradit. Mělo by tak být mnohem snadnější provozování různých aplikací či dokonce frameworků na jednom webu. Každá vrstva by mohla požadavek odmítnout, třeba router by mohl legitimně vyhodit chybu 404. Zároveň by nové řešení mělo být plně kompatibilní se stávajícími presentery, takže bude možné vytvářet aplikace využívající jak nových middleware-presenterů, tak i presenterů současných.

Tato změna architektury vyřeší řadu issues, které ve frameworku existují už dlouho, ale nebylo snadné je zprovodit ze světa:

Další plány

Alespoň bodově se pokusím zmínit další plány pro důležité balíčky. Ve které verzi budou implementované je zatím otevřené.

Application

  • nativní presenter pro REST a API
  • oddělit routování do samostatného balíčku
  • snadnější používání LinkGenerator pod CLI

Application\UI

  • nahradit přímé odesílání HTTP hlaviček (přes HttpResponse) abstrakcí
  • zrychlit generování odkazů kešováním
  • možnost vkládat bloky šablony do komponent
  • CSRF ochrana pro odkazy
  • pokusit se odstranit parametr _fid
  • vyřešit problém s odkazem this nebo formulářem v Error presenteru
  • nativní podpora oprávnění na úrovní presenterů a akcí, makro n:if-allowed
  • cmdline nástroj pro předgenerování presenterů a šablon

DI

  • autodiscovery: automatická registrace služeb pomocí masky, podobně jako u presenterů
  • validovatelné schéma pro formát NEON
  • podpora pro předání pole služeb určitého typu (@param Service[])
  • podpora pro předání pole služeb pomocí tagu
  • umožnit rozšířením registrovat vlastní options (jako inject:, autowired:) v definicích služeb
  • vyřešit problém s prioritou volání rozšíření

Forms

  • vytvořit nativní podporu pro plně dynamické formuláře
  • podpora pro nativní validaci přes AJAX
  • automatická CSRF ochrana
  • provázání na třídy entit, setDefaults() bude přijímat a getValues() vracet objekt entity
  • upravit objektový návrh tak, aby setValue() a getValue() mohly mít správné typehinty
  • připravit ukázku rendereru pro Bootstrap 4

Latte

  • vytvoření nového AST parseru, který posune možnosti maker na novou úroveň
  • možnost spojení šablony se třídu (a jejími properties), aby fungovalo našeptávání nebo statická kontrola i v šabloně
  • implementovat sandbox režim
  • zjednodušit ověřování validity šablon
  • generovat „source map“ – kterým bude rozumět Tracy
  • doplnit podporu pro další knihovny a frameworky, vytvořit návody nebo převodníky pro ostatní šablonovací systémy

Tracy

  • podpora source maps

Web a forum

  • v plánu je změnit navigaci a rozčlenění podle jednotlivých komponent
  • nahradit fórum za nějaké novější, už kvůli bezpečnosti
  • a samozřejmě postupné vylepšování dokumentace jako doposud

Je toho prostě opravdu hodně.

Kdy to bude?

Cílem je vydávat novou velkou verzi každý rok na podzim. To bude možné za předpokladu, že se získá plné financování, o což usiluje nový crowdfundingový program. V případě nenaplnění cílů by byl vývoj značně pomalejší a možnosti limitované, proto tento plán berte jako reálný v případě naplnění cílů.


Nette spouští ambiciozní program

Nette spouští nový crowdfundingový program, jehož cílem je získat finanční prostředky pro vývoj frameworku. Hlavní změnou je, že místo jednorázových příspěvků je zaměřen na pravidelnou měsíční podporu. Ta může přicházet jak od jednotlivců, tak od firem, kterým nabízí možnost zviditelnit se na webu Nette a inzerovat na fóru nebo přímo v dokumentaci, tedy na místech s nejlepším zásahem cílové skupiny programátorů.

Každý, kdo staví na Nette, má zájem, aby se framework aktivně vyvíjel. Aby podporoval nové verze PHP. Aby se opravovaly chyby. Aby přicházel s dalšími novinkami, které usnadní práci nebo ušetří čas a peníze. Aby framework měl skvělou dokumentaci a existoval kolem něj užitečný obsah, ať už ve formě článků, návodů nebo videí.

Řada částí Nette představuje světovou špičku a chceme, aby tomu tak bylo nadále.

Bez adekvátního financování se nic z toho nedá zajistit. Přitom abyste se mohli spolehnout, že vyjdou další verze, stačí docela málo: abyste jej každý měsíc podpořili byť jen malou finanční částkou.

Pojďte do toho a staňte se partnerem Nette!

Používá vaše firma Nette?

Pokud pracujete ve firmě, které Nette vydělává peníze, vysvětlete prosím svému šéfovi, že je dobrý nápad se stát partnerem a zajistit tak zdravé fungování projektu, na který spoléháte. Zvýšíte prioritu řešení vašich issues a zároveň zviditelníte svoji společnost v komunitě a přilákáte k sobě vývojáře.

Partnerství totiž přichází s exkluzivními výhodami. Například s uvedením vašeho loga na webu Nette, možností vkládat pracovní nabídky, inzerovat na fóru (ukázka) nebo v dokumentaci (ukázka), tedy na místech se zcela nejlepším zásahem do skupiny Nette vývojářů.

Partnerům vystavujeme faktury, aby si mohli podporu dát do nákladů, a to buď měsíčně, čtvrtletně, půlročně nebo ročně.

Používáte Nette soukromě?

Stojíme také o vaši podporu. Přihlaste se k pravidelným donations přes PayPal. Vaše jméno bude vidět na webu Nette.

Kolik peněz je potřeba?

V tuto chvíli máme stanoveny tři cíle, kterých chceme dosáhnout.

Prvním cílem je 64.000 Kč měsíčně, které zajistí, že se vývoji bude věnovat jeden programátor na půl úvazku. Další vývoj Nette tak bude pokračovat, nicméně polovičním tempem než dosud, což není úplně ideální. Při dosažení hranice 128.000 Kč získá framework full-time vývojáře a nové verze mohou přicházet každý rok.

Náklady jsou tak nízké proto, že nepotřebujeme kanceláře a hlavně pracujeme na něčem, co nás baví.

Při dosažení třetí mety 196.000 Kč měsíčně budeme moci přizvat další spolupracovníky a vylepšovat tak web, dokumentaci, vytvářet nový obsah a nechat jej překládat do angličtiny. A tak oslovit zahraniční komunitu. Čtvrtá meta dává možnost zapojit druhého programátora, což by vývoj a správu issues značně zrychlilo.

Jakmile se dosáhne tohoto milníku, připravíme další cíle, které by počítaly s více programátory, mohly by vznikat nové užitečné nástroje a knihovny. Pak třeba vznikne i kniha o Nette.


Viz také Freelo.cz: 14 důvodů, proč podpořit Nette


Dibi 4: sluší mu přísný kabát?

Už je to šílených dvanáct let, co jsem na tomto blogu představil knihovnu Dibi. Dnes se dočkala čtvrté verze. Využívající všech předností moderního PHP 7.1.

Vývoj a testování verze 4.0 trvalo 11 měsíců a čítá 114 komitů, zapojilo se do něj několik autorů, kterým děkuji.

A co je nového? Dibi 4 běží v přísném režimu, tedy s declare(strict_types=1). Je plně typovaná, tedy parametry a návratové hodnoty metod mají nativní typehinty. Což si vyžádalo jednu drobnou změnu: metody fetch() nebo fetchSingle() v případě, že už v tabulce není další řádek, vracejí nově null na místo false, protože návratové hodnoty mohou být nullable, nikoliv falseable. Doplněná byla podpora pro JSON (automaticky dekóduje tyto sloupce), kontroluje, zda s modifikátorem %i nebo %f nepoužijete řetězec, co není číslo, přibyl Dibi\Expression a Connection::expression() (příklad), interface IConnection a spousta dalších drobností.

Protože Composer se dnes považuje za standard, jak instalovat balíčky, archív ZIP i s minifikovanou verzí zmizel v propadlišti dějin.

Změnou je, že metody Connection::query() a Fluent::execute() nevrací v případě DML příkazů počet ovlivněných řádek, ale objekt Dibi\Result. Počet řádek zjistíte z něj ($res->getRowCount()) nebo jako dříve ($connection->getAffectedRows()).

Dále objekt Dibi\DateTime je nyní potomkem DateTimeImmutable . Má v sobě implementovaný magický mechanismus, který by měl odhalit, pokud někde v kódu stavíte na tom, že je mutable, a došlo by tak k chybě.

Pak jsem dal pryč několik historických reliktů nebo zbytečností, kompletní přehled najdete v changelogu. Ač ten seznam může vypadat dlouze, v praxi byste krom výše zmíněného neměli na žádný BC break narazit.

A ještě pro úplnost: Dibi od verze 3.1 podporuje mikrosekundy, což může ve specifickém případu vést k BC breaku (viz vlákno) a od verze 3.2 podporuje jen třídy s namespaces (tedy krom třídy dibi).

Co bude dál?

Určitě zajímavé by bylo do Dibi doplnit podporu pro nativní bindování parametrů, třeba pro upload binárních souborů je to nutnost. A s tím úzce souvisí i prepared statements. Občas zaznívají žádosti o vylepšení fluent interface, volání uložených procedur atd.

Zcela na rovinu říkám, že budoucnost stojí zejména na tom, jestli budu mít za Dibi nějaké příspěvky. Takže pokud máte Dibi rádi, nastavte prosím měsíční donation a svět bude nadále krásný :-D


How to Mock Final Classes?

How to mock classes that are defined as final or some of their methods are final?

Mocking means replacing the original object with its testing imitation that does not perform any functionality and just looks like the original object. And pretending the behavior we need to test.

For example, instead of a PDO with methods like query() etc., we create a mock that pretends working with the database, and instead verifies that the correct SQL statements are called, etc. More e.g. in the Mockery documentation.

And in order to be able to pass mock to methods that use PDO type hint, it is necessary for the mock class to inherit from the PDO. And that can be a stumbling block. If the PDO or method query() were final, it would not be possible.

Is there any solution? The first option is not to use the final keyword at all. This, of course, does not help with the third-party code that it uses, but mainly detracts from the important element of the object design. For example, there is dogma that every class should be either final or abstract.

The second and very handy option is to use BypassFinals, which removes finals from source code on-the-fly and allows mocking of final methods and classes.

Install it using Composer:

composer require dg/bypass-finals --dev

And just call at the beginning of the test:

require __DIR__ . '/vendor/autoload.php';

DG\BypassFinals::enable();

Thats all. Incredibly black magic :-)

BypassFinals requires PHP version 5.6 and supports PHP up to 7.2. It can be used together with any test tool such as PHPUnit or Mockery.


This functionality is directly implemented in the „Nette Tester“: https://tester.nette.org version 2.0 and can be enabled this way:

require __DIR__ . '/vendor/autoload.php';

Tester\Environment::bypassFinals();

Jak mockovat final třídy?

Jak mockovat třídy, které jsou definované jako final nebo některé z jejich metod jsou final?

Mockování znamená nahrazení původního objektu za jeho testovací imitaci, která neprovádí žádnou funkcionalitu a jen se tváří jako původní objekt. A předstírá chování, které potřebujeme kvůli testování.

Takže například místo objektu PDO s metodami jako query() apod. vytvoříme jeho mock, který práci s databází jen předstírá, a místo toho ověřuje, že se volají ty správné SQL příkazy atd. Více třeba v dokumentaci Mockery.

A aby bylo možné mock předávat metodám, které mají type hint PDO, je potřeba, aby i třída mocku dědila od PDO. A to může být kámen úrazu. Pokud by totiž třída PDO nebo metoda query() byla final, už by to nebylo možné.

Existuje nějaké řešení? První možnost je final vůbec nepoužívat. To ovšem nepomůže s kódem třetích stran, který final používá, ale hlavně se tím ochuzujeme o důležitý prvek objektového návrhu. Existuje dogma, že každá třída by měla být buď final, nebo abstract.

Druhou a velmi šikovnou možností je použít Nette Tester, který od verze 2.0 disponuje vychytávkou, která odstraňuje z kódu klíčové slovo final on-the-fly. Stačí na začátku testu zavolat:

require __DIR__ . '/vendor/autoload.php';

Tester\Environment::bypassFinals();

A je to. Je za tím ukrytá neskutečně černá magie :-)

Pokud nepoužíváte Nette Tester, ale třeba PHPUnit, nebudete ochuzeni, stačí si nainstalovat BypassFinals:

composer require dg/bypass-finals --dev

A na začátku skriptu zavoláte:

require __DIR__ . '/vendor/autoload.php';

DG\BypassFinals::enable();

Jak souhrnně nazývat třídy a rozhraní?

Názvoslovný oříšek: jak souhrnně označovat třídy a rozhraní? Jak třeba nazvat proměnnou, která může obsahovat jak název třídy, tak rozhraní? Co zvolit místo $class?

Dá se tomu říkat type ($type), nicméně to je zase příliš obecné, protože typem je i řetězec nebo pole. Z pohledu jazyka jím může být i něco komplikovanějšího, třeba ?array. Navíc je sporné, co je v případě objektu jeho typ: je jím název třídy, nebo je to object?

Nicméně souhrnné označení pro třídy a rozhraní skutečně existuje: je jím slovo třída.

Cože?

  1. Z pohledu deklarace je interface hodně ořezaná třída. Může obsahovat jen veřejné abstraktní metody. Což také implikuje nemožnost vytvářet objekty. Rozhraní jsou tedy podmnožinou tříd. A pokud je něco podmnožinou, tak to můžeme označovat názvem nadmnožiny. Člověk je savec, stejně jako rozhraní je třída.
  2. Nicméně je tady ještě pohled užití. Třída může dědit jen od jedné třídy, ale může implementovat vícero rozhraní. Nicméně tohle je omezení týkající se tříd, samotné rozhraní za to nemůže. Obdobně: třída nemůže dědit od final třídy, ale přitom final třídu pořád vnímáme jako třídu. A také pokud třída může implementovat víc rozhraní (tj. tříd, viz 1.), stále je vnímejme jako třídy.

A co traity? Ty sem vůbec nepatří, z hlediska OOP jednoduše neexistují.

Tedy problém se společným pojmenováním tříd a rozhraní je vyřešen. Říkejme jim prostě třídy.

classes + interfaces = classes

No jo, ale vznikl tady problém nový. Jak říkat třídám, které nejsou rozhraní? Tedy jejich doplňku. Tomu, co se ještě na začátku článku nazývalo třídy. Nerozhraní? Nebo implementations:-)

To je ještě větší oříšek. To je pořádný ořech. Víte co, raději zapomeňme na to, že rozhraní jsou také třídy, a tvařme se opět, že každý OOP identifikátor je buď třída, nebo rozhraní. Bude to snazší.


phpFashion © 2004, 2019 David Grudl | o blogu

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