Showing posts with label unit test. Show all posts
Showing posts with label unit test. Show all posts

Wednesday, February 29, 2012

Az Art-Work.hu története (Murányi S.)

Az Art-Work (http://art-work.hu) története 2007-ben kezdődött el, amikor egy szakdolgozat témájaként jött létre. Mivel én magam is szeretek fotózni, így nem csak a szakdolgozat viszonylag hamar kikerült a netre (deviantart, stockexpert ihletésére). Reklámozni akkoriban csak iwiw-en reklámoztam, illetve készült pár száz szórólap, amit pár városban művészeti iskolákban szórtunk ki.

2007-2009 -ig három verziót élt meg az oldal. A verzió váltás okai a szakmai fejlődésem volt elsősorban.
Az első nagyobb lépés a 2009-ben PRADO-ra váltás volt, amikor már némi web2 is bekerült az oldalba, és mégis csak egy összeszedettebb rendszer volt, mint az addigiak. A PRADO-t nagyon szerettem, mert jó volt használni, megvoltak a megfelelő dolgok, nem kellett semmiben feltalálni a spanyol viaszt. 

2009-2011-ig sajnos nem nagyon volt foglalkozva az oldallal az akkori munkahelyem miatt, ezért a felhasználói bázis szép lassan el is felejtette az oldalt. Így az addig összegyűlt 1000 felhasználó és 8000 kép ott maradt.
2011 végén, munkahely váltás után lett szabadidőm ismét, és az akkor már általam használt Yii-ben gondoltam, hogy újra írom az oldalt.

Így az már működik és ismét zajlik az élet :)

A Yii-re azért váltottam PRADO-ról, mert ugyan az MVC nagyon új volt a PRADO felfogásával szemben, ám sebességben olyan szakadék tátongott a kettő között, hogy egyértelmű volt a váltás. Megnyugtató volt számomra, hogy a Yii egyik fejlesztője a PRADO volt alapítója, így egyértelmű volt, hogy minőségi keretrendszer ez is.

Yii-ben kihasználok mindent, amit lehet, de azért pár dolog, aminek nem tetszik a működése, azt inkább magam készítem. Ilyen például az assetek kezelése. Nem szimpatikus, hogy egy mappában random dir-be kerülnek az assetek, és ha kiviszem production-be az alkalmazást, akkor új dir-ek generálódnak. Inkább én belinkelem magamnak, ami éppen kell. Remélem ezt valamikor átalakítják.

Nem szoktam használni a grid-et például, inkább saját foreach ciklust használok, mert felesleges extra lépcsőként tekintek erre a lehetőségre.

Kiegészítettem a rendszert egy saját DataType osztállyal és annak leszármazottjaival, mellyel igyekszem a PHP laza típus kezelését kivédeni. AJAX kommunikációnál és minden klienstől REQUEST-ben érkező adatnál kialakítottam egy DTO (Data Transfer Object) rendszert, ahol előre definiált adatok közlekednek az objektumok között, nem csak feltöltöm a model attributes property-jét a POST-ból érkező adattal. Így pl, mivel külön class-ban van mondjuk egy form-tól várt mezők, jól látható, hogy mi az, és csak az, amit a klienstől várunk. Nagyjából a CFormModel is ezt oldja meg (ha jól tudom), próbáltam is használatát, de kevésbé volt szimpatikus, mint a saját megoldásom.

Ha elakadok, sokszor segít a Yii fórumban keresgélés, de a stackoverflow-n is nagyon sok Yii-s problémára találok megoldást.

Amit viszont örömmel használok a Yii alap lehetőségeiből és letölthető extension-jaiból: EAjaxUpload, urlManager, YiiMail, I18N

Amit még fontosnak tartok megemlíteni, mert sokat könnyít a fejlesztésen, az az, hogy az I18N modult kiegészítettem egy wrapper osztállyal, amit ugyanúgy kell használni, mint a Yii által adott default tool-t, annyi különbséggel, hogy nem kell magunknak belerakni az új fordítandó szavakat az adott dictionary php-ba, hanem magától belerakja, ha még nincs benne, illetve a file-t is létrehozza, ha még nincs. Ez a felfogás a PRADO-ból jött, mert ott is nagyon tetszett, hogy ezt megcsinálja helyettem. Ezt a Yii fórumon meg is osztottam természetesen.

Jelenleg a UNIT tesztek írását készítem, ahol azért néha rájövök, hogy nem teljesen sikerült minden részt tesztelhetőre készíteni. Ezért is alakítottam ki a DTO környzetet például.

-- írta  Murányi Sándor

Tuesday, December 28, 2010

Auto TDD avagy PHPUnit magától

Ez a kis tipp nemcsak Yii fejlesztőknek ajánlott, hanem akárkinek aki a PHPUnit-ot használ Linux-os környezetben. (elvileg Apple-lel is működnie kell, de nem próbáltam!) -- Javítás: NEM MŰKÖDIK Apple-lel!

A következő egyszerű utasítással 15 másodpercenként lefuttathatjuk egységtesztjeinket. (protected/tests)

watch -n 15 -d phpunit unit/
Az én képernyőm valahogy így néz ki (bal sarokban fut a teszt!)


Katt nagyobb képhez!

Nagyobb tesztek esetén persze lehet finomítani, hogy csak 1 teszt file fusson .., stb.

Az eredeti cikk itt található: http://www.yiiframework.com/wiki/124/auto-tdd-how-to-auto-run-phpunit/

Saturday, September 11, 2010

Környezetbarát Egységtesztelés Meztelencsigákkal


avagy, hogyan gyártsunk Egységtesztelős programot a Yii-vel.

Mi ez?

Ha netán a címből nem lenne eléggé egyértelmű, a következőkben Egységteszetkről (Unit Test) lesz szó PHP-s környezetben. Ha a nyájas olvasó még nem olvasta a korábban megjelent Yiiki al'a Yii cikket, és semmilyen tapasztalata nincs a Yii keretrendszerrel, mindenképp érdemes rajta átfutni ugyanis itt nem fogunk belemenni az alapvető, -Na most akkor, hogy is kezdjem? kérdésekbe.

Mi lesz ez?

Egy nagyon egyszerű URL rövidítőt fogunk késziteni, mint pl a http://tinyurl.com vagy a http://vurl.me. Ha valaki nem ismerné, a lényeg az, hogy marha hosszú URL-eket lekicsinyítünk, és ha valahol egy felhasználó az általunk lekicsinyített linkre (slug) kattint, azt továbbítjuk a megfelelő oldalra.

Bevezetés

Környezet:

  • PHP 5.2.x
  • Apache (a PHP futtatásához)
  • SQLite (az egyszerűség kedvéért használjuk ezt, lehetne MySQL is)
  • Yii 1.1.3 (de elvileg működik az 1.1-es ág bármelyik csomagjával)
  • PHPUnit (tesztelést végrehajtó program: http://www.phpunit.de )

Fogalmak:

Test Driven Development (TDD) - Tesztelésen alapuló fejlesztés.
Ezt talán úgy lehetne a legegyszerűbben bemutatni, hogy képzeljünk el egy adott problémát és próbáljuk meg visszafelé megoldani. Oké, lehet, hogy ez így elsőre hülyén hangzik, de talán ez az ábra segíthet.
  1. Írjunk tesztet
  2. Ellenőrizzük, hogy a tesztünk hibázik (FAIL)
  3. Írjuk meg (a legegyszerűbben!) a kívánt kódot úgy, hogy a tesztünk sikeres legyen.
  4. Ellenőrizzük, hogy a tesztünk sikeres e (PASS)
  5. Módosítsuk a kódunkat (úgy, hogy a teszt még mindig sikeres maradjon!)
  6. Kezdjük előlről az egészet.
Unit Test - Egységteszt.
Az egységek, komponensek tesztelése, hogy megbizonyosodjunk a működésének helyességéről. Cél, hogy feltárja nincsenek-e tévműködések, feltáratlan hibák a belső algoritmusban, adatkezelésben. A komponensek más rendszer komponensektől függetlenül vannak tesztelve. (http://www.dcs.vein.hu/)
Ez így elsőre, másodikra de talán még harmadikra is érdekesen hangozhat, de reméljük a cikk végén minden világos lesz.
(ja, itt ajánlanám Erenon írását.)

Adatbázis

Mint korábban említettük, SQLite3-at fogunk használni, de gyakorlatilag bármilyen adatbázis motorral is műküdik a dolog.

Táblák

protected/data/rovidke.db
++++++++++++++++++++++++++
+ urls                   +
++++++++++++++++++++++++++
+ id INT (auto_increment)+
+ url TEXT               +
+ slug VARCHAR(50)       +
+ created INT            +
++++++++++++++++++++++++++
SQLite3
CREATE TABLE urls (created INTEGER, id INTEGER PRIMARY KEY AUTOINCREMENT, slug varchar(50), url TEXT);
Na, ennyi pepecselés után ugorjunk is bele a programozásba. Miután sikeresen létrehoztuk az alap alkalmazásunkat (pl: yiic webapp rovidke). Állítsuk is be egyből az adatbázis kapcsolatot.
protected/config/main.php
...
  'connectionString' => 'sqlite:'.dirname(__FILE__).'/../data/rovidke.db',
...
Itt szeretném megjegyezni, hogy többször is belefutottam a következő hibaüzenetbe (SQLite esetén, Linux-szal, lehet, hogy ez MS környezetben nem gond)
CDbCommand failed to execute the SQL statement: SQLSTATE[HY000]: General error: 8 attempt to write a readonly database
Ez annyit jelent, hogy az adatbázis file-t a program nem tudta írásra megnyitni, tehát át kell állítanunk a jogokat (ha csak a saját gépemen programozok, és nem foglalkozok a biztonsággal, egyszerűen 777-et adok neki :) )

Itt a MVC

Modell

Első lépésként hozzuk is létre a Url modellünket. Ha valaki nagyon bátor, az használhatja az új csodafegyvert: Gii, amit a yiic utódjának szán a Yii fejlsztői csapat. Tulajdonképpen egy grafikus, bongészőbarát kódgeneráló eszköz. (a cikkben a yiic-et fogjuk használni)
./protected/yiic shellYii Interactive Tool v1.1 (based on Yii v1.1.3)
Please type 'help' for help. Type 'exit' to quit.
>> _
>> model Url urls
   generate models/Url.php
   generate fixtures/urls.php
   generate unit/UrlTest.php
The following model classes are successfully generated:
    Url
If you have a 'db' database connection, you can test these models now with:
    $model=Url::model()->find();
    print_r($model);
>>
Akiknek jobb a szeme, azok nyilván észrevették a teszt csomagot, amit a Yii alapból elkészít minden modellhez a protected/tests/unit mappa alatt. Gyakorlás képpen Futtassuk is le "tesztjeinket" (protected/tests/) ...
$phpunit unit...
There was 1 error:
1) testCreate(UrlTest)
CDbException: The table "urls" for active record class "Url" cannot be found in the database.
...
Hoppá, hát az meg mi? Mi az, hogy nincs ilyen tábla az adatbázisban?
Azt fontos megjegyezni, hogy tesztelés közben nem egy, csak és kizárólag, a teszthez írt funkciót futtatunk, hanem az éles alkalmazás kódját. Ami azt jelenti, hogy a funkció az mindig ugyanaz marad és mindig ugyanúgy fut le. Hova is akarok ezzel kilyukadni? A lényeg, hogy amikor a teszteket futtatjuk, a program egy előre legyártott adat-tömbből (fixtures) veszi az adatokat, ezeket egy adatbázisba teszi, lefutattja a tesztet, és törli a DB táblákban tárolt bejegyzéseket. Tehát fontos, hogy az éles adatbázis és a teszt adatbázis különböző legyen!
Innen jött a hibaüzenet, ugyanis a teszt adatbázisunk még nem létezik. Csináljunk is egyet ...
protected/data/rovidke_test.db
++++++++++++++++++++++++++
+ urls                   +
++++++++++++++++++++++++++
+ id INT (auto_increment)+
+ url TEXT               +
+ slug VARCHAR(50)       +
+ created INT            +
++++++++++++++++++++++++++
SQLite3
CREATE TABLE urls (created INTEGER, id INTEGER PRIMARY KEY AUTOINCREMENT, slug varchar(50), url TEXT);
... és módosítsuk a teszt(!) konfigurációs file-t az alábbiak szerint
protected/config/test.php
...
 'connectionString'=>'sqlite:' . dirname(__FILE__).'/../rovidke_test.db',
...
ha, most lefuttatjuk a tesztünket ... protected/tests/
$phpunit unitPHPUnit 3.3.16 by Sebastian Bergmann.
.
Time: 0 seconds

OK (1 test, 0 assertions)
minden OK! Birkabőr (Juhéjj)!
Kedvenc szövegszerkesztőnkkel nyissuk meg a UrlTest? a protected/tests/unit/ könyvtárból (aki VIM-et hasznal +1 piros pontot kap). Ez elvileg üres, illetve, az osztályt a Yii már létrehozta, és talán akad is egy példa teszt. Ha van, ha nincs, a lényeg, hogy minden teszt funkciónak a testcimkével kell kezdődnie. Nem történik semmi baj, ha nem kezdjük ezzel a tesztünket, de nem fog lefutni. A gyakorlás kedvéért csináljunk is két tesztet ...
protected/tests/unit/UrlTest?.php
...
    public function testTrue()
    {
        $this->assertTrue( false );
    }

    public function testTrue2()
    {
        $this->assertTrue( true );
    }
...
Ez talán elég világos, de ha nem, akkor röviden annyi történik, hogy elsőben megvizsgáljuk, hogy az IGAZ az HAMIS e, a másodikban pedig, hogy az IGAZ az IGAZ e. Futtasuk is le ...
protected/tests/
$phpunit unit
PHPUnit 3.3.16 by Sebastian Bergmann.

F.
Time: 0 seconds
There was 1 failure:
1) testTrue(UrlTest)
Failed asserting that <boolean:false> is true.
/var/www/rovidke/protected/tests/unit/UrlTest.php:18

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.
Jajj de remek, láthatjuk is, hogy egy darab (F) betűt kaptunk, ami a tévhitekkel ellentétben, az jelenti, hogy FAIL (ja, a pont . meg azt, hogy a teszt PASS(ED), azaz sikeresen lefutott.) Ha jobban megvizsgáljuk az üzenetet, akkor még az is kiderül, hogy Failed asserting that <boolean:false> is true, ami a Google fordító szerint annyit jelent, hogy Elmulasztotta azt állítja, hogy hamis igaz. Remélem lejön a lényeg :)
Rengeteg ilyen "a teszt azt állítja, hogy" funkció létezik, ebben a példában mi csak néhányat fogunk bemutatni/használni. A teljes listáért ide klikkoljatok.

Fixtures - Minta Adat

Mint már korábban említettem, a "teszt csomag" egy úgynevezett adat-tömbből veszi a teszthez szükséges adatokat, most itt erről lesz szó (egy kicsit). Szerencsénkre Yii már létrehozta az alap könyvtárrendszert és a file-okat, sőt még néhány példa tömbböt is készített aprotected/tests/fixtures/urls.php file-ban. Pompás.
Módosítsuk is az értékeket, valahogy így:
return array(
        'sample1'=>array(
                'created' => '1234567890',
                'slug' => 'AA',
                'url' => 'http://weblabor.hu',
        ),
        'sample2'=>array(
                'created' => '1234567890',
                'slug' => 'AB',
                'url' => 'http://google.com',
        ),
);
Ugyan ezek az adat-tömbök elhanyagolhatóak, a gyakorlatban szinte mindig hasznaljuk őket.

Megint Tesztelünk

Nyissuk meg a URL teszt file-unkat és írjunk gyorsan egy számláló tesztet, ami megszámolja az összes linket a táblában:
protected/tests/unit/UrlTest?.php
...
    public function testCountAll()
    {
        $urls_count = sizeof(Url::model()->findAll());
        $this->assertEquals( 2, $urls_count );
    }
...
A példa kedvéért itt az assertEquals()-t mutatom be, ami azt "Feltételezi", hogy a két megadott érték megegyezik. Ezzel lehet játszadozni, ha valami nem stimmel a tesztünk úgyis elbukik. PL:
protected/tests
$phpunit unit/
...
  Failed asserting that <integer:2> matches expected value <integer:1>.
...
Ugye milyen egyszerű? Az assertEquals()-ba nem csak szám értéket passzolhatunk ám, hanem akár szöveges változót is:
protected/tests/unit/UrlTest?.php
...
    public function testActionView()
    {
        $url = Url::model()->findByAttributes( array( 'id' => 1 ) );

        $this->assertTrue( $url instanceof Url );
        $this->assertEquals( $url->url, 'http://altavizsla.hu' );
    }
...
ez nyilván elbubik, de ...:
protected/tests/
$phpunit unit...
There was 1 failure:

expected string <http://weblabor.hu>
difference      <       xxxxxxxxxxx??>
got string      <http://altavizsla.hu>
...
Itt a PHPUnit ügyesen megmutatja nekünk, hogy pontosan hol is tértek el a szöveges változók. jajj, de jó :)
Na most nézzünk egy kicsit komolyabb tesztet. A urls táblába csak olyan rekordot szertnénk elmenteni, aminek mind a url, mind a slug értéke be van állítva, tehát nem lehet üres. Írjuk meg a tesztet:
protected/tests/unit/UrlTest?.php
...
    public function testUrlRequired()
    {
        $this->assertTrue( Url::model()->isAttributeRequired( 'url' ) );
    }
...
Ha most lefuttatnánk a tesztünket, akkor nyilvánvalóan elbukna. Szerencsénkre a Yii modell osztálynak van egy ún. rules() (szabályok) funkciója, amivel többek között azt is beállíthatjuk, hogy mely értékek nem lehetnek üresek. Ezt a required-del határozhatjuk meg.
protected/models/Url.php
...
  public function rules()
  {
    return array(
       ...
       array( 'url', 'required' ),
       ...
    );
  }
...
A tesztünk már majdnem sikeres ;) Hasonlóan az előző példához, most állítsuk be a slug-ot is ...
protected/tests/unit/UrlTest?.php
...
    public function testSlugRequired()
    {
        $this->assertTrue( Url::model()->isAttributeRequired( 'slug' ) );
    }
...
TESZT: FAIL!
protected/models/Url.php
...
    return array(
       ...
       array( 'url,slug', 'required' ),
       ...
    );
...
Itt még azt is észrevehetjük, hogy milyen szépen lehet több értéket is megadni a Yii rules()-ban.
TESZT: PASS!
Most nézzünk egy olyan példát, ahol a tesztelésre szánt funkció még nem létezik. Például mi történik akkor, ha egy új URL-t (inkább slug-ot) szeretnénk készíteni. Ha most próbálnánk ki a programot, akkor kézzel kéne beállítanunk a lerövidített URL-t. Na ne má ... Tehát ha az adatbázisunk üres (nincs 1 darab URL sem) akkor az első slug legyen: AA. Na hogy is néz ez ki pontosan?
protected/tests/unit/UrlTest?.php
...
    public function testCreateUrl()
    {
        $url = new Url();
        $url->setAttribute( 'url', 'http://altavizsla.hu' );
        $this->assertTrue( $url->save(), 'URL tarolas tesztelese' );
        $this->assertEquals( $url->slug, 'AA' );
    }
...
A tesztünk az feltételezi, hogy egy új URL esetén az új URL slug-ja az AA értékkel fog visszatérni. A teszt itt nyilván elbukik. (azt érdemes megjegyezni, hogy több feltételezést is meg lehet adni 1 teszten belül).
Itt jön segítségünkre, a beforeValidate() beépített függvény, ami arra szolgál, hogy még a mezők ellenőrzése előtt(!) futtassunk le valamit ...
protected/models/Url.php
    public function beforeValidate()
    {
        $this->slug = $this->createSlug();
        return parent::beforeValidate();
    }

    public function createSlug()
    {
       return 'AA'; 
    }
Azért nem tudtuk itt a beforeSave()-et használni, mert az ellenőrző szabályunk (rule) elbukna, ugyanis a slug nem lehet üres. Most csak azt akarjuk elérni, hogy a tesztünk sikeresen lefusson, ugyanis a createSlug() nem adhat mindig AA értéket, pl. ha több URL-t is szeretnénk tárolni az adatbázisunkban ;). Ha most lefuttatjuk a tesztünket akkor sikeresen lefut. Jajj, de jó. Na most módosítsuk a kódunkat úgy, hogy az tényleg azt csinálja amit akarunk ...
    public function createSlug()
    {
        $slug = Url::model()->find( array( 'order' => 'slug DESC' ) )->slug;

        if( ! $slug )
        {
            $slug = 'AA';
        }
        else
        {
            ++$slug;
        }

        return $slug; 
    }
Futtassuk a tesztet ...
protected/tests/
$phpunit unit/
...
Failed asserting that two strings are equal.
expected string <AC>
difference      < x>
got string      <AA>
...
Ez meg hogy lehet, az előbb még minden jó volt ;) Persze a fentiekből egyből kiderül, hogy a teszt az AA értéket várta és AC-t kapott. Tehát módosítanunk kell a tesztünket. Itt jogosan merülhet fel a kérdés, hogy mi a ráknak ez a kerülőút? De ne feletjtsük el, tesztelünk.
protected/tests/unit/UrlTest?.php
...
    public function testCreateUrl()
    {
        $url = new Url();
        $url->setAttribute( 'url', 'http://altavizsla.hu' );
        $this->assertTrue( $url->save(), 'URL tarolas tesztelese' );
        $this->assertEquals( $url->slug, 'AC' );
    }
...
Még itt gyorsan, mielőtt elfelejtem, a createSlug()-ot rövidíthetjük így is.
protected/models/Url.php
...
    public function createSlug()
    {
        $slug = Url::model()->find( array( 'order' => 'slug DESC' ) )->slug;
        return $slug ? ++$slug : 'AA';
    }
...

Vezérlő és Nézet Cucc - CRUD - Létrehoz, Megjelenít, Frissít, Töröl

Az azért remélem feltűnt, hogy még egyszer sem ellenőriztük a programot a böngészőben. Mert nincs is rá szükség. Abban azonban biztosak lehetünk, hogy a URL modellre vonatkozó funkcióink majdnem 100%-osak. Azért nem mondom, hogy majdnem, mert több mindent lehetne, sőt, kellene(!) még tesztelnünk. Pl. mi van akkor, ha frissítünk, vagy törlünk egy linket az adatbázisból stb. Ezeket a fentiek alapján, remélem, kis gondolkodás után meg lehet csinálni. De még van néhány egyéb teendőnk is mielőtt a programunkat a felhasználók karmai közé eresztenénk.
Készítsük el az alap felhasználói interfészt (formok, nézetek és egyebek) a yiic segítségével.
protected/yiic shell
>> crud Url
   generate UrlController.php
   generate UrlTest.php
      mkdir /var/www/rovidke/protected/views/url
   generate create.php
   generate update.php
   generate index.php
   generate view.php
   generate admin.php
   generate _form.php
   generate _view.php
   generate _search.php
Ha egy új felhasználó érkezik az oldalra, azt szeretnénk, hogy minden klikkolgatás nélkül készíthessen egy rövidített linket (monnyuk ez nem csakúj felhasználókra vonatkozik). Módosítsuk az alap kontrollerünket:
protected/config/main.php
...
    'defaultController'    => 'url/create',
...
Amikor a Yii a hozzáférési szabályokat készíti, alapból a létrehozás funkció (create) csak belépés után lehetséges. Ami jelen esetben nem elfogadható, hiszen mi azt szeretnénk, hogy a felhasználóink a lehető leggyorsabban létrehozhassanak rövidített linkeket. Módosítsuk tehát a
protected/controllers/UrlController?.php
...
   public function accessRules()
... 
   array('allow',  // allow all users to perform 'index' and 'view' actions
        'actions'=>array('index','view', 'create'),
        'users'=>array('*'),
   ),
   array('allow', // allow authenticated user to perform 'create' and 'update' actions
        'actions'=>array('update'),
        'users'=>array('@'),
   ),
...
Ha most megnéznénk a kis programunkat, akkor mindenféle egyéb form mezőket látnánk, amit az okos (már tesztelt) Modell funkcióink szépen kitöltenek a URL mentése előtt. (pl. dátum mező, a rövidített URL azaz slug mező). Tulajdonképpen csak egyetlen mezőre van szükségünk, a URL-re:
protected/views/url/form.php
...
    <?php /*
        <div class="row">
                <?php echo $form->labelEx($model,'created'); ?>
                <?php echo $form->textField($model,'created'); ?>
                <?php echo $form->error($model,'created'); ?>
        </div>

        <div class="row">
                <?php echo $form->labelEx($model,'slug'); ?>
                <?php echo $form->textField($model,'slug',array('size'=>50,'maxlength'=>50)); ?>
                <?php echo $form->error($model,'slug'); ?>
        </div>
    */ 
    ?>

        <div class="row">
                <?php echo $form->labelEx($model,'url'); ?>
                <?php echo $form->textField($model,'url', array( 'size' => 50 )); ?>
                <?php echo $form->error($model,'url'); ?>
        </div>
...
Miután programunk elmenti az új Url-t, Yii automatikusan betölti a megjelenítő kódot (viewAction()). Mi azt szeretnénk, ha a felhasználónk valami ilyesmit látnának: http://rovidke.hu/QWD, és erre klikkelve (vagy a böngészőbe másolva) jutnának el az igazi URL-hez.
Először módosítsuk a megjenítő nézetet:
protected/views/url/view.php
...
<?php 
    echo 
        CHtml::link( 
            $_SERVER['SERVER_NAME'] . '/' . $model->slug, 
            $this->createUrl( '/' . $model->slug ) 
        );
?>
...
<?php /*
$this->widget('zii.widgets.CDetailView', array(
        'data'=>$model,
        'attributes'=>array(
                'created',
                'id',
                'slug',
                'updated',
                'url',
        ),
)); 
*/
?>
No, és itt jön az alkalmazás egy másik érdekessége, az átirányítás vagy redirect. Alapértelmezésben a rendszer a kapott URL alapján megpróbál egy Vezérlőt (Controller) keresni és átpasszolni neki a kapott változókat. Nekünk viszont az kéne, hogy ha valami ilyesmit kapunk (/QWD) akkor a program ne keresse a QWD-t (nem találna!) hanem hívja meg a Url vezérlőt és adja át QWD-t mint egy változó értéket. (ugyebár a slugazonosítója.) Ezt az alap konfigurációs file-ban tehetjük meg, a következő képpen (a szabályok sorrendje fontos):
protected/config/main.php
...
        'urlManager'=>array(
             'urlFormat'=>'path',
             'showScriptName' => false,
             'rules'=>array(
                 '/<slug:[A-Z]+>' => 'url/redirect',
                 '<controller:\w+>/<id:\d+>'=>'<controller>/view',
                 '<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
                 '<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
             ),
         ),
...
Persze mindez nem jöhetne létre ha nics apache (.htaccess). Ha az alkalmazás ROOT könyvtárában valami oknál fogva nem lenne meg ez a file, akkor az alábbiak alapján kíszítsünk egyet.
.htaccess
Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on

#Uncomment "RewriteBase /" when you upload this .htaccess to your web server, and comment it when on local web server

#NOTE: 

#If your application is in a folder, for example "application". Then, changing the "application" folder name, will require you to reset the RewriteBase /[your app folder]

#RewriteBase /[your app folder - optional]

# if a directory or a file exists, use it directly
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d

# otherwise forward it to index.php 
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]
Na, most már tényleg csak egy dolog van hátra, megírni magát az átirányító funkciót actionRedirect()
protected/controllers/UrlController?.php
...
    public function accessRules()
    {
       ...
          array('allow',  // allow all users to perform 'index' and 'view' actions
                  'actions'=>array('index','view','create','redirect'),
                  'users'=>array('*'),
                ),    
       ...
    }
...
    public function actionRedirect()
    {
        $slug = Yii::app()->request->getParam( 'slug', null );
        if( $slug )
        {
            $url = Url::model()->findByAttributes( array( 'slug' => $slug ) );
            if( $url )
            {
                $this->redirect( Url::model()->findByAttributes( array( 'slug' => $slug ) )->url );
            }
        }

        throw new CHttpException( '404', 'hmmm ... slug not found!' );
    }
...

Még nagyon sok mindent lehetne finomítani a programunkon, de szerintem kezdetnek ez is elég. Azt pedig nagyon remélem, hogy a PHP közösség (végre) felébred és használni fogja a Teszt Alapú (TDD) programozást.
A Yii legyen veletek.

"Ismeret az nem cselekedet, csakis cselekedet a cselekedet" - tdc10