Yii mieša karty PHP frameworkov

Yii logo

Framework Yii (yes it is) je pomerne mladým hráčom na poli PHP frameworkov. Má však šancu miešať kartami na preplnenom trhu s frameworkami pre tento jazyk. Panuje okolo neho stále väčší ruch a záujem komunity okolo neho narastá. Poďme si ho preto v stručnosti predstaviť.

Autorom Yii frameworku je Qiang Xue, čínsky programátor ktorý okrem iného stojí za PHP frameworkom PRADO. Verzia 1.0 bola vydaná 3.Dec 2008 po takmer ročnom vývoji. Na domovskej stránke o ňom autor vyhlasuje, že je rýchly, bezpečný a profesionálny, určený pre vývoj WEB 2.0 aplikácií. Stavia nad štandardným konceptom MVC, pričom obsahuje vlastné riešenie pre modelovú vrstvu s využitím patternu Active Record.

Je bohato vybavený a pritom veľmi kompaktný (light-weight), poskytne vám nástroje pre:

  • abstraktný aj objektový prístup k databáze
  • validáciu užívateľských vstupov (validátory sú spoločné pre formuláre aj Model)
  • ACL
  • i18n a l10n
  • cache vrstva (aj fragment caching)
  • transparentnú podporu komponentov jQueryUI (AJAX, serverový kód, widgety pre šablóny)
  • podporu pre skiny a themy
  • jednotkové a funkčné testy
  • scaffolding pre automatické generovanie kódu

V neposlednom rade je treba spomenúť dokumentáciu, ku ktorej patrí vynikajúca užívateľská príručka, Wiki, API referenčná príručka alebo niekoľko tutoriálov. K štúdiu Yii je dostupná aj tlačená kniha Agile Web Application Development with Yii 1.1 and PHP5. Online pomoc je možné hľadať na oficiálnom fóre alebo na IRC. Yii má aj veľmi živý twitter kanál, na ktorom takmer každý deň pribudne niekoľko tipov a trikov, ktoré komunita zapísala do Wiki.

Framework až nezvykle tlačí vývojárov k využívaniu konzoly. V stiahnutom archíve sa prekvapivo nenachádza žiadny skeleton pre začatie nového projektu, pritom však framework vyžaduje pomerne striktnú adresárovú štruktúru. Nový projekt je preto nutné vytvoriť pomocou scaffoldingu – spustením konzolovej aplikácie v zložke s frameworkom:

$ cd /var/www/projectx/framework
$ ./yiic webapp ../www_root

Tento príkaz pripraví skeleton nového projektu s niekoľkými Views a widgetmi pre dynamické menu a drobečkovú navigáciu

Adresárová štruktúra projektu v Yii vypadá takto:

Samotný kód aplikácie sa nachádza v zložke www_root/protected. Názvy pod zložiek nepotrebujú komentár. Zložka protected ma v kontexte projektu špeciálny význam – patrí pod jeden z aplikačných aliasov, konkrétne application. Aliasov je niekoľko, využívajú sa hlavne pri práci s filesystémom (napr. upload obrázkov). Filesystem cestu z aliasu získame kdekoľvek statickým volaním

Yii::getPathOfAlias(NAZOV_ALIASU);

Zoznam všetkých systémových aliasov:

system cesta k Yii frameworku
application cesta k zložke protected (application’s base directory)
webroot cesta k document_root webovej aplikácie, (zložka s  index.php)
zii cesta to knižnice zii (okrem iného jQuery UI komponenty)
ext cesta k zložke pre 3rd party knižnice

Metódy pre prácu s aliasmi dovoľujú pracovať s hierarchiou zložiek pomocou bodkovej konvencie. Napr. import konkrétnej triedy môžeme vykonať pomocou:

Yii::import('ext.Zend.Pdf.Color');
Yii::import('ext.Zend.Pdf.*'); // importuje celu zlozku

Yii používa jednoduchý autoloader, prehľadávané cesty sa nastavujú v konfiguračnom súbore (v prípade webovej aplikácie  protected/config/main.php).

'import'=>array(
    'application.models.*',
    'application.components.*',
    'application.extensions.*',
),

Statická trieda Yii predstavuje univerzálneho, všade dostupného pomocníka, ktorý umožňuje získať zaujímavé objekty kdekoľvek v projekte (dnes sa však už tento koncept považuje za prekonaný, do popredia vstupuje technika Dependency in­jection). Najčastejšie budete pravdepodobne využívať volanie Yii::app(), ktoré získa bežiacu aplikáciu. Webovú reprezentuje objekt CWebApplication, ktorý poskytuje napr. tieto properties (komponenty):

request CHttpRequest obálka nad prišlým HTTP požiadavkom
session CHttpSession nízko úrovňová obálka nad PHP session
user CWebUser vysoko úrovňová obálka nad PHP session, abstrahuje návštevníka stránky
urlManager CUrlManager dvojsmerný URL router
clientScript CClientScript manažér JS a CSS

Na tomto mieste spomeňme, že v Yii sú všetky triedy (okrem Yii) prefixované C (viď. vyššie). Obmedzí sa tak kolízia s vašimi triedami a triedami iných knižníc.

Nový projekt obsahuje vlastnú kópiu aplikácie yiic v zložke protected. Pre scaffoldovanie v rámci samotného projektu je nutné využívať tento! Aplikácia yiic poskytuje ešte niekoľko ďalších príkazov okrem webapp, stačí ju spustiť bez parametrov

  • message – vyhľadá nepreložené frázy vo Views a vygeneruje prekladové súbory vo forme PHP arrays
  • migrate – podpora pre databázové migrácie
  • shell – interaktívny shell v bežiacom Yii projekte
  • webapp – vytvorenie nového projektu

Za zmienku stojí príkaz shell, ktorý poskytuje vlastnú sadu príkazov:

Yii v tomto veľmi pripomína veľké frameworky ako Symfony alebo RoR a umožňuje pomerne mocné prototypovanie aplikácie. Pomocou niekoľkých príkazov v konzoli je možné vytvoriť jednoduché administračné rozhranie (CRUD nad tabuľkami), stačí správne nastaviť pripojenie k databáze v konfiguračnom súbore.

Ako sme už spomenuli, interaktívny shell beží v rámci Yii projektu, je teda možné spúšťať napr. metódy nad vašimi Modelmi. Samotné vytvorenie Modelu z existujúcej databázovej tabuľky a získanie záznamov z tabuľky je otázkou dvoch príkazov v interaktívnom shelli:

Scaffold automaticky pripravil aj podklady pre unit testovanie Modelu. Projekt je možné prototypovať aj pomocou webového rozhrania nazvaného Gii, ktoré je webovou kópiou interaktívneho shellu. Stačí len odkomentovať príslušnú sekciu v hlavnom konfiguračnom súbore, nastaviť heslo a otvoriť správnu adresu v browseri.

Ešte sa zastavíme pri konzoli. Yii umožňuje vytvárať vlastné konzolové aplikácie rovnako pohodlne ako tie webové. Konzolové controllery reprezentované potomkami CConsoleCommand predstavujú nové konzolové príkazy, ktoré pre vás spúšťa konzolový scafollder  yiic:

<?php // protected/commands/NewsletterCommand.php

class NewsletterCommand extends CConsoleCommand
{
    public function run($args)
    {
        if (empty($args)) {
            die("nAvaiable command(s): send, prepare-newnn");
        }

        switch ($args[0]) {
            case 'send':
                echo "sending newslettern";
                break;

            case 'prepare-new':
                echo "creating new newslettern";
                break;

            default:
                die("nERROR! Wrong or no command specified. Avaiable command(s): send, prepare-newnn");
              break;
        }
    }
}

ORM a formuláre

Ako sme už spomenuli vyššie, Yii disponuje vlastným ORM pre Modely. Využíva pattern Active Record – každý vytvorený Model zastupuje databázovú tabuľku. Nie je však úplne pravda, že Modely môžu byť iba databázového pôvodu. Pre generické Modely Yii ponúka triedu CModel, podstatne zaujímavejšie sú však odvodené typy: CActiveRecord a CFormModel. Pozrime sa najskôr na prvý.

CActiveRecord umožňuje klasické:

$application = new Application;
$applicaion->name = 'Sample application';
$appliction->author_id = '4';
$application->save();

Je treba upozorniť, že pred uložením automaticky prebehne validácia Modelu (možné zakázať prvým parametrom metódy save()). Pravidla sú uložené v samotnej definícii triedy Modelu ako PHP array (v Yii je všetko konfigurovateľné uložené ako PHP array). Scaffolder pri vytváraní nového Modelu nastaví vhodné pravidlá na základe definície stĺpcov v databázovej tabuľke:

public function rules()
{
    return array(
        array('name, author_id', 'required'),
        array('author_id',       'numerical', 'integerOnly'=>true),
        array('name',            'length', 'max'=>120),
    );
}

S pravidlami súvisia aj tzv. scenária Modelu. Modelu je možne oznámiť kontext, v akom sa má validovať. Napr. pri registrácii nového užívateľa, Modelu User nastavíme scenário insert, kedy sú vyžadované polia password a passwordRepeat. Naopak, keď užívateľ edituje svoj profil v administrácii, nevyplnené polia znamenajú, že užívateľ si nepraje meniť heslo a polia sa preto nebudú validovať. Modelu to oznámime nastavením scenária update. Validačné pravidlá môžu v takomto prípade vypadať takto:

public function rules()
{
    return array(
        array('email', 'required'),
        array('password, passwordRepeat', 'required', 'on'=>'insert'),
        ...
    );
}

Pohodlie programátora je dokonca zvýšené faktom, že hore uvedené dve scenária sú pre neho nastavované automaticky:

  • pri inštancovaní Modelu sa nastaví insert
  • pri načítaní Modelu z databáze sa nastaví update

Nad procesom však samozrejme máte plnú kontrolu:

$user = new User('registration');
if ($user->validate()) {
    // TODO

    $user->setScenario('vote');
    if ($user->validate()) {
        // TODO
    }
}

Pre viac detailov o validačných pravidlách si prosím naštudujte problematiku v užívateľskej príručke, kde okrem iného nájdete aj zoznam dostupných validátorov.

Pre načítavanie Modelov z DB poskytuje Yii celú plejádu find* metód. Najskôr však treba získať živú inštanciu Modelu:

$aplication1 = Application::model()->findByPk(1);
$aplications = Application::model()->findAll('author_id=:authorId', array(':authorId'=>4));

Všimnite elegantnú syntax techniky parameter bin­ding. Pre pokročilejšie dotazovanie je dostupný tzv. query builder:

$c = new CDbCriteria;
$c->condition = 'author_id=:authorId';
$c->order = 'name DESC';
$c->params = array(':authorId'=>4);

$applications = Application::model()->findAll($c);

Relačné ORM

WEB 2.0 aplikácie zhusta pracujú s previazanými údajmi v databázach, preto podpora pre relačné vzťahy nesmie v Yii ORM chýbať. CActiveRecord podporuje 4 druhy relácií:

  • BELONGS_TO, objekt je vlastnený iným objektom, N:1, napr. aplikácia má jedného autora
  • HAS_MANY, objekt vlastní mnoho podradených objektov, 1:N, autor má mnoho aplikácií
  • HAS_ONE, špeciálny prípad HAS_MANY, kde nadradený objekt môže vlastniť iba jeden podriadený objekt
  • MANY_MANY, typický M:N vzťah, aplikácia môže byt vo viacerých kategóriách, kategória môže obsahovať mnoho aplikácií

Ak vaša databáza podporuje referenčnú integritu a mate správne nastavené FK’s, môžete sa spoľahnúť že scaffold processing Modelov nastaví vaše triedy správne (relačné vzťahy je potrebné zapísať do tried Modelov). V prípade MANY_MANY je pri scafoldingu krajných Modelov automaticky rozpoznaná väzobná tabuľka. Model pre túto tabuľku nie je potrebné vytvárať. Pre ukladacie mechanizmy bez podpory FK je možné väzby zapísať ručne:

public function relations()
{
    return array(
        'author' => array(self::BELONGS_TO, 'User', 'author_id'),
        'tags' => array(self::MANY_MANY, 'Tags', 'applications_tags(application_id, tag_id)'),
    );
}

Scaffolding predsa len urobí drobnú chybku pri generovaní tried – v relačnom pravidle, v druhom prvku (odkazovaná trieda) neustále vyplňuje plurál (Users)! Dajte si prosím na to pozor.

Toto v Modeli vytvorí virtuálne properties, ktoré svoj obsah získajú lazy, pri prvom prístupe k takejto relačnej property. Ak vyžadujete načítanie relačných údajov už pri fetchnutí hlavného objektu (objektov), Yii vám poskytuje techniku nazvanú eager loading:

$applications = Application::model()->with('author')->findAll();

Do kategórie vyšší dívči už potom možno zaradiť SELECT podľa relačného stĺpca:

$c = new CDbCriteria;
$c->with = 'author';
$c->condition = "author.name='John Doe'";

$applications = Application::model()->find($c);

formuláre

Pre vývojára je najdôležitejšia možnosť definovať, vykresliť a spracovať formulár. Vynikajúca správa – v Yii je možné formuláre vykresľovať z inštancií CActiveRecord! Nemusíte ich teda nijako definovať. Dokonca ani vytvárať nejaké extra validačné pravidlá. Vďaka faktu, že formulár je reprezentácia Modelu vo View, má vývojár ušetrené množstvo práce. Postranným efektom toho je, že typ a vzhľad formulárového políčka je definovaný vo View šablone, tak ako je to najsprávnejšie. Príklad takej šablóny:

<div class="form">
  <?php $form = $this->beginWidget('CActiveForm', $options = array('id'=>'applicationForm')); ?>
    <div class="row">
      <?php echo $form->labelEx($application,'name'); ?>
      <?php echo $form->textField($application,'name'); ?>
      <?php echo $form->error($application,'name'); ?>
    </div>

    <div class="row">
      <?php echo $form->labelEx($application,'author'); ?>
      <?php echo $form->dropDownList($application, 'author', $application->getAuthorsDLdata()); ?>
      <?php echo $form->error($application,'author'); ?>
    </div>

    <div class="row buttons">
      <?php echo CHtml::submitButton('save'); ?>
    </div>
  <?php $this->endWidget(); ?>
</div>

Upozorníme len na riadok s definíciou selectboxu (dropDownList). Vyžaduje naplniť PHP poľom so schémou value=>display. Takéto pole je najvhodnejšie získať zo samotného primárneho Modelu (alebo staticky z relačného Modelu):

Class Application extends CActiveRecord
{
    ...

    public function getAuthorsDLdata()
    {
        $authors = Author::model()->findAll();
        $out = array();
        foreach ($authors as $author) {
            $out[$author->id] = $author->name;
        }

        return $out;
    }

    ...
}

Spracovanie formulára v Yii sa zvyčajne vykonáva v rovnakej akci, ktorá ho vykreslila. Vďaka tomu je vykreslenie validačných chýb prirodzené a nevyžaduje zbytočnú logiku.

Class SiteController extends CController
{
    ...

    public function actionUpdateApplication($id)
    {
        $application = Application::model()->findByPk($id);

        if (Yii::app()->request->getIsPost() && isset($_POST['Application'])) {
            $application->attributes = $_POST['Application'];
            if ($application->validate()) {
                $application->save(false);

                $this->user->setFlash('info', 'Update succeeded.';
                $this->redirect(array('/site/updateApplication', 'id'=>$id));
                Yii::app()->end();
            }
        }

        $this->render('updateApplication, array(
            'application'=>$application,
        ));
    }

    ...
}

Za mierne neintuitívne a nemoderné možno považovať manuálne naplnenie Modelu prijatými údajmi priamo z $_POST. Napriek tomu je však operácia bezpečná a novými hodnotami su prepísané len polia, ktoré majú definované validačné pravidlá. Nestane sa tak, ze by útočník mohol podvrhnúť request, v ktorom posiela napr. ID záznamu, a ten tak prepísal. Ak by predsa len nastala potreba meniť propery Modelu bez definovaného validačného pravidla, označte pole ako safe vo validačných pravidlach.

public function rules()
{
    return array(
        ...
        array('id', 'safe'),
        ...
    );
}

CFormModel

Ako sme už spomenuli vyššie, Modely nemusia mať len persitentné pozadie. Občas nastane potreba vytvoriť formulár bez akejkoľvek väzby na databázu, napr. formulár na vyhľadávanie. Takýto formulár je nutné manuálne definovať ako potomka  CFormModel:

<?php

class SearchForm extends CFormModel
{
    public $search;

    public function rules()
    {
        array('search', 'required'),
        array('search', 'lenght', 'min'=>3),
    );
}

S takto definovaným formulárom potom pracujeme (vykreslenie, spracovanie) úplne rovnako ako s CActiveRecord, tak ako sme naznačili vyššie.

View šablóny, routovanie

Každý správny MVC framework obsahuje mechaniku vykresľovania View šablón a Yii nie je výnimkou. Implementuje klasické dvojfázové vykresľovanie zvnútra=>von, tj. šablóna-akcie=>layout. View šablónu sme si už ukázali pri vykresľovaní formulára. Mohli ste si všimnúť, že sa jednalo iba o klasické PHP značkovanie. Yii skutočne neposkytuje žiadny šablónovací jazyk a do vetvy 1.1 sa ani neplánuje. Nakoľko ani neimplementuje žiadny response objekt, nie je možné v šablónach využívať filtre. Dôležitým faktom je, že $this v šablónach ukazuje na aktuálny Controller. Môžeme tak aspoň z časti využívať helpre vo forme jeho metód. Existuje aj veľmi užitočný pomocník (helper container) – CHtml, ktorý obsahuje množstvo statických metód pre každodennú potrebu.

Najčastejšie v šablónach narazíte na potrebu vykresliť predané údaje a generovanie linkov na HTTP akcie. Predávanie údajov do šablóny sme mohli vidieť v príklade spracovania formulára, keď sme do šablóny predali inštanciu objektu Application. Pri vykresľovaní dynamických údajov je treba dbať na bezpečnosť a údaje pred vypísaním ošetriť:

<?php echo CHtml::encode($application->name); ?>

Generovanie linkov z routra je možné riešiť dvojako:

<a href="<?php echo $this->createUrl(ROUTA, $urlParams=array()); ?>">text linku</a>

alebo

<?php echo CHtml::link(TEXT_LINKU, array(ROUTA, $urlParams) $htmlOptions=array()); ?>

Routu predstavuje destinácia, na ktorú link mieri vo forme /modul/controller/akcia. Modul nie je povinné uvadzať, ak nepoužívate modulárnu aplikáciu alebo nechcete opúšťať aktuálny modul. Príklad linku aj s dodatočnými parametrami:

<a href="<?php echo $this->createUrl('/news/create', array('lang'=>$this->lang)); ?>">pridať novinku</a>

Takéto vytváranie URL bude správne reflektovať zmeny vo vašich routovacích maskách. Konfigurácia masiek je uložená v protected/config/main.php, ale jej popis je nad rámec článku. V užívateľskej príručke nájdete vyčerpávajúci výklad problematiky.

Widgety

Aj s widgetom sme sa stretli pri vykresľovaní formulára. Widget si možno predstaviť ako vykresliteľný blok s vlastnou akciou a šablónou. Do View šablóny sa umiestni volaním:

<?php $this->widget('FooWidget', $properties=array('uid'=>$user->id)); ?>

Najjednoduchší widget by potom mohol vypadať

<?php

class FooWidget extends CWidget
{
    public $uid;

    public function run()
    {
        $this->render('fooWidget', array(
            'user' => User::model()->findByPk($this->uid)
        ));
    }
}

šablóna

<div id="fooWidget">
  <?php echo  CHtml::encode($user->name);?>
</div>

Widgety je vhodné umiestniť do zložky protected/widgets, šablóny prídu do protected/widgets/views. V konfiguračnom súbore alebo inde v projekte je potom nutné zaistiť ich import do aplikácie.

Zhrnutie

Yii je veľmi bohatý framework. V tomto sumáre sme pokryli len základné vlastnosti a schopnosti, ktoré sú nutné pre prvé kroky s frameworkom. Vyznačuje sa kompaktnosťou, výkonom ale hlavne výborne spracovanou dokumentáciou. Vývoj Yii je veľmi živý a narastajúcu popularitu dokazuje aj umiestnenie v nedávnom experimente, kde bola určená popularita webových frameworkov podľa množstva topicov na Stack Overflow.

Za nevýhody možno považovať absenciu šablónovacieho jazyka, nepoužívanie najmodernejších techník (response objekt, statická trieda Yii), ale aj neprehľadné kombinovanie anglických a čínskych topicov vo výsledkoch vyhľadávania na oficiálnom fóre.

Yii si istotne zaslúži pozornosť a istotne sa o svoju pozíciu pobije. Nech aj tento článok prinesie jeho väčšiu popularitu v česko-slovenských kotlinách.

Webdeveloper (PHP, Javascript) momentálne u Websupport.sk. Fanúšik moderných technológií. Neváha podeliť sa o svoje znalosti, podľa motta „opensource knowledge“.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 84

Přehled komentářů

G Re: Yii mieša karty PHP frameworkov
sidik Super
marek debugger
sidik Re: debugger
srigi Re: debugger
Vrtak-CZ Re: debugger
HosipLan Dobré!
srigi Re: Dobré!
Martin Soušek Proč RoR v PHP, když můžu mít RoR v Ruby?
sidik Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
vetesnik Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
Pepa Chmel Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
Pepa Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
Jerry12 Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
Jerry12 Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
Pepa Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
Tharos Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
Pepa Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
Rubysta Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
Michal Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
Hmmm Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
Fontyš Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
. Re: Proč RoR v PHP, když můžu mít RoR v Ruby?
ujovlado dobry clanok
Michal Diky za clanek
Tharos A co na to Nette :)?
LV Re: A co na to Nette :)?
cleb Nejsem fanouškem frameworků, ale...
Nox C prefix
Nox Re: C prefix
Hmmm Re: C prefix
Tharos Re: C prefix
Hmmm Re: C prefix
Čelo Re: C prefix
David Grudl Re: C prefix
Hmmm Re: C prefix
blizz Re: C prefix
fos4 Re: C prefix
langpa jazyk
blizz Re: C prefix
ud Re: C prefix
František Kučera Re: C prefix – Kanón
imploder Re: C prefix
plistiak Jazyková korektúra
LV Re: Jazyková korektúra
Martin Malý Re: Jazyková korektúra
srigi Re: Jazyková korektúra
Martin Malý Re: Jazyková korektúra
Michal Re: Jazyková korektúra
plistiak Re: Jazyková korektúra
plistiak Re: Jazyková korektúra
marek pokračovanie?
Pepa Re: pokračovanie?
Martin Malý Re: pokračovanie?
Pepa Re: pokračovanie?
Martin Malý Re: pokračovanie?
Pepa Re: pokračovanie?
Michal Re: pokračovanie?
Opravdový odborník :-) Re: pokračovanie?
Pepa Re: pokračovanie?
Michal Re: pokračovanie?
David Grudl Předsudky, dojmy, průjmy
Ondřej Mirtes Re: Předsudky, dojmy, průjmy
anonym Re: Předsudky, dojmy, průjmy
Ondřej Mirtes Re: Předsudky, dojmy, průjmy
anonym Re: Předsudky, dojmy, průjmy
Pepa Re: Předsudky, dojmy, průjmy
Hmmm Re: Předsudky, dojmy, průjmy
blizz Re: Předsudky, dojmy, průjmy
František Kučera Re: Předsudky, dojmy, průjmy
Martin Soušek Re: Předsudky, dojmy, průjmy
Michal Re: Předsudky, dojmy, průjmy
petr dunaj Re: Předsudky, dojmy, průjmy
Hmmm Re: pokračovanie?
Pepa Re: pokračovanie?
Hmmm Re: pokračovanie?
Hugo Re: pokračovanie?
Stefano Moja skúsenosť
Extak Také něco k Yii
Radoslav Ruky prec
drla Re: Ruky prec
lubos Re: Ruky prec
moo yii re-generovani
Extak Re: yii re-generovani
Zdroj: http://www.zdrojak.cz/?p=3490