ugrás a tartalomhoz

Wiki à la Yii

imehesz · 2010. Május. 30. (V), 19.33
Wiki à la Yii

Ebben a rövidke leírásban azt szeretnénk bemutatni, milyen egyszerű elkészíteni egy elegáns webes alkalmazást a Yii keretrendszer segítségével. Ha a későbbiekben igény lesz rá, természetesen belevághatunk kényesebb témákba is, mint például a biztonság, gyorsítótárazás, komolyabb Active Record használat stb.

Bevezetés

Fogalmak

Itt felsorolnék néhány alapvető (és igen fontos) fogalmat, amelyek ismerete szükséges:

Objektumorientált programozás (PHP 5)
A jelenleg stabil Yii 1.1-es verzió a PHP 5.1-en alapszik, tehát annak és objektumorientált programozási lehetőségeinek ismerete mindenképp szükséges.
Relációs adatbázis (SQL)
A Yii sokféle relációs adatbázist támogat. A példában én az SQLite-ot választottam, mert nem igényel különösebben bonyolult beállításokat.
MVC (Model–View–Controller) avagy Modell–Nézet–Vezérlő
A Yii egy MVC mintát szorosan követő rendszer, ez dióhéjban annyit jelent, hogy az alkalmazás jól elkülöníthető három részre: A modellre, ami az adatbázis-lekérdezéseket végzi, a view-ra vagy nézetre, ami a megjelenítésről gondoskodik, s végül a controller-re vagy vezérlőre, amely kapcsolatot teremt az előző kettő között, s feldolgozza a felhasználó által bevitt információt.
Active Record
Az Active Record minta megszünteti alkalmazásunk függőséget a különböző adatbázisoktól, a táblákat és sorokat objektumokként kezeli. Ez a gyakorlatban azt jelenti, hogy mondjuk fejlesztési környezetben használhatunk SQLite-ot, élesben pedig MySQL-t vagy MSSQL-t.

Környezet

A programot a következő konfigurációval készítettük:

  • Ubuntu Linux (9.10)
  • Apache 2.x
  • SQLite 3
  • PHP 5.2.x
  • Yii 1.1.x

Csomagoljuk ki a letöltött Yiit a web szerverünk főkönyvtárába (pl. /var/www/yii/). A forráskód majd szintén a /var/www/ alá kerül (pl. /var/www/yiiki/).

Az is fontos, hogy a Yii rendszer a parancssorból is futtatható legyen! (PHP CLI)

Az adatbázis felépítése

CREATE TABLE pages (
  id       INTEGER PRIMARY KEY,
  title    VARCHAR(125),
  body     TEXT,
  revision INTEGER,
  created  INTEGER
);

Az alkalmazás létrehozása

Az alap alkalmazás létrehozása rendkívül egyszerű, csak adjuk ki a kovetkező utasítást (a webszerver főkönyvtárában állva):

imehesz@imehesz-laptop:/var/www$ ./yii/framework/yiic webapp yiiki
Create a Web application under '/var/www/yiiki'? [Yes|No] yes
      mkdir /var/www/yiiki
      mkdir /var/www/yiiki/themes
      mkdir /var/www/yiiki/themes/classic
      mkdir /var/www/yiiki/themes/classic/views
      mkdir /var/www/yiiki/themes/classic/views/layouts
      mkdir /var/www/yiiki/themes/classic/views/system
   generate themes/classic/views/.htaccess
      mkdir /var/www/yiiki/themes/classic/views/site
   generate index.php
      mkdir /var/www/yiiki/css
   generate css/bg.gif
   generate css/form.css
   generate css/screen.css
   generate css/main.css
   generate css/ie.css
   generate css/print.css
      mkdir /var/www/yiiki/assets
      mkdir /var/www/yiiki/images
      mkdir /var/www/yiiki/protected
      mkdir /var/www/yiiki/protected/extensions
   generate protected/yiic.bat
      mkdir /var/www/yiiki/protected/models
   generate protected/models/LoginForm.php
   generate protected/models/ContactForm.php
   generate protected/yiic.php
      mkdir /var/www/yiiki/protected/data
   generate protected/data/schema.sqlite.sql
   generate protected/data/schema.mysql.sql
   generate protected/data/testdrive.db
      mkdir /var/www/yiiki/protected/controllers
   generate protected/controllers/SiteController.php
   generate protected/yiic
      mkdir /var/www/yiiki/protected/config
   generate protected/config/main.php
   generate protected/config/test.php
   generate protected/config/console.php
      mkdir /var/www/yiiki/protected/commands
      mkdir /var/www/yiiki/protected/commands/shell
      mkdir /var/www/yiiki/protected/messages
   generate protected/.htaccess
      mkdir /var/www/yiiki/protected/views
      mkdir /var/www/yiiki/protected/views/layouts
   generate protected/views/layouts/main.php
      mkdir /var/www/yiiki/protected/views/site
   generate protected/views/site/login.php
      mkdir /var/www/yiiki/protected/views/site/pages
   generate protected/views/site/pages/about.php
   generate protected/views/site/index.php
   generate protected/views/site/contact.php
   generate protected/views/site/error.php
      mkdir /var/www/yiiki/protected/runtime
      mkdir /var/www/yiiki/protected/tests
   generate protected/tests/WebTestCase.php
   generate protected/tests/phpunit.xml
   generate protected/tests/bootstrap.php
      mkdir /var/www/yiiki/protected/tests/report
      mkdir /var/www/yiiki/protected/tests/functional
   generate protected/tests/functional/SiteTest.php
      mkdir /var/www/yiiki/protected/tests/fixtures
      mkdir /var/www/yiiki/protected/tests/unit
      mkdir /var/www/yiiki/protected/components
   generate protected/components/Controller.php
   generate protected/components/UserIdentity.php
   generate index-test.php

Your application has been created successfully under /var/www/yiiki.

Ha minden jól sikerült, akkor a Your application has been created successfully under /var/www/yiiki üzenet jelenik meg, ami röviden annyit jelent, hogy az alap program sikeresen létrehozva a megadott mappa alatt.

Azt már itt érdemes megjegyezni, hogy a programunk már ebben a fázisban működőképes. Ha megtekintjük a URL-t egy böngészőben (pl. http://localhost/yiiki) akkor (remélhetőleg) egy üdvözlőképernyő fogad bennünket.

Az alkalmazás összekapcsolása az adatbázissal

A wikik többnyire nagyon egyszerű, mondhatnánk butácska programok, amik semmi mást nem csinálnak, csak oldalakat tárolnak és mutatnak meg. A problémát többféleképpen is meg lehetne oldani, még adatbázis használata nélkül is! De mi szeretnénk bemutatni, hogy mennyire egyszerű adatbázisokat kezelni Yiivel.

A fő konfigurációs állomány a protected/config/ mappa alatt található, és main.php a neve. Nyissuk is meg a kedvenc szövegszerkesztőnkkel! (Mármint amelyiket programozásra használunk). Aki egyből a vimre gondolt, az kap egy piros pontot.

Módosítsuk az alábbiak szerint (fontos: az adatbázis állományt a protected/data/ alá tettük):

'db' => array(
    'connectionString' => 'sqlite:protected/data/yiiki.sqlite',
),

És ennyi.

A yiic, avagy a Yii varázspálcája

Természetesen lehetne a fejlesztést a yiic teljes hanyagolásával folytatni, de használata mindenképpen megkönnyíti a programozást. A __ROOT__ könyvtárunkban egyszerűen adjuk ki a következő utasítást:

imehesz@imehesz-laptop:/var/www/yiiki$ ./protected/yiic shell
Yii Interactive Tool v1.1 (based on Yii v1.1.0)
Please type 'help' for help. Type 'exit' to quit.
>> _

Ha járatosak vagyunk valamelyik adatbáziskezelő parancssori programjával, akkor ez ismerős lehet. Segítséget a help [utasitas]-sal lehet kérni. Vágjunk is bele.

A Pages modell létrehozása a yiic segítségével

Egyszerűen adjuk ki a következő parancsot (még mindig a shellben vagyunk!):

>> model Page pages
   generate models/Page.php
   generate fixtures/pages.php
   generate unit/PageTest.php

The following model classes are successfully generated:
    Page

If you have a 'db' database connection, you can test these models now with:
    $model=Page::model()->find();
    print_r($model);

>>

Nos, itt elég sok minden történt, szűrjük ki, ami nekünk fontos lehet.

model/Page.php – ez nagyon fontos, ez lesz a modellünk, ami majd az adatbázis-lekérdezéseket végzi.

fixtures/pages.php
unit/PageTest.php

Sajnos ez utóbbi kettővel nem fogunk foglalkozni (legalábbis ebben a cikkben nem), annyit érdemes megjegyezni, hogy az egységteszteléshez nyújt segítséget.

A parancs lefutása után a program arról is értesít minket, hogy ha be van állítva az adatbázis kapcsolatunk, akkor ki is próbálhatjuk. Egyelőre tudjuk, hogy semmi nincs az adatbázisunkban, szóval nem sok értelme lenne a lekérdezésnek.

Mi is az a CRUD?

Azzal, hogy elkészítettük a Page modellünket, még semmi böngészőben látható eredményt nem sikerült produkálnunk. No és itt jön a híres CRUD (create, read, update, delete) a képbe. Ezeket az utasításokat az adatbázisunk (pages táblánk) egy-egy rekordján fogjuk alkalmazni.

A vezérlő és nézet létrehozása a crud paranccsal

Még mindig a shellben állva adjuk ki a következő parancsot:

>> crud Page
   generate PageController.php
   generate PageTest.php
      mkdir /var/www/yiiki/protected/views/page
   generate create.php
   generate update.php
   generate index.php
   generate view.php
   generate admin.php
   generate _form.php
   generate _view.php

Crud 'page' has been successfully created. You may access it via:
http://hostname/path/to/index.php?r=page

>>

Aha, itt már lényegesen több minden történt mint a modell létrehozásánál. Láthatjuk, hogy maga a vezérlő is elkészült, ezen felül egy újabb tesztfájl, a view/page/ mappa is, ahol a rendszer fogja tárolni a nézeteket (create.php, update.php, view.php stb).

Megjegyzés: a vezérlő külön is elkészíthető a controller page paranccsal.

Gyors áttekintés

Programunk jelenlegi állapotában mindenre kész. Tudunk oldalakat listázni, készíteni és törölni. Hogy megbizonyosodjunk róla, böngészőnkkel látogassuk meg a http://localhost/yiiki/index.php?r=page URL-t.

Na de mi nem elégszünk meg ennyivel, hiszen egy igazi wikit szeretnénk készíteni. Jöhet a testreszabás.

Alkalmazásunk testreszabása

A beviteli űrlap módosítása

Ha most probálnánk egy oldalt készíteni (http://localhost/yiiki/index.php?r=page/create), akkor azonnal észlelnénk, hogy a beviteli űrlaponon egy csomó olyan mező van, amit nem szeretnénk minden egyes alkalommal kézzel kitölteni, illetve elvárnánk a programunktól, hogy saját maga töltse ki. Ilyen például a created vagy a revision mező.

Módosítsuk az űrlapunkat (protected/views/page/_form.php) az alábbiak szerint:

<div class="form">
	<?php echo CHtml::beginForm(); ?>
		<p class="note">Fields with <span class="required">*</span> are required.</p>
		<?php echo CHtml::errorSummary($model); ?>
		<div class="row">
			<?php echo CHtml::activeLabelEx($model, 'title'); ?>
			<?php echo CHtml::activeTextField($model, 'title', array('size' => 60, 'maxlength' => 125)); ?>
			<?php echo CHtml::error($model, 'title'); ?>
		</div>
		<div class="row">
			<?php echo CHtml::activeLabelEx($model, 'body'); ?>
			<?php echo CHtml::activeTextArea($model, 'body', array('rows' => 6, 'cols' => 50)); ?>
			<?php echo CHtml::error($model, 'body'); ?>
		</div>
		<div class="row buttons">
			<?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>
		</div>
	<?php echo CHtml::endForm(); ?>
</div><!-- form -->

A Page modell szabályai

A modellünkre (protected/models/Page.php) is ráfér egy kis farigcsálás. A yiic többnyire kitalálta, hogy milyen szabályokat hozzon létre a különböző mezőkhöz, de a gondolatolvasástól azért még messze van. Mi ugyanis azt szeretnénk, hogy az oldalunk címében csakis betűk, számok vagy aláhúzás jel szerepelhessen. (Egy jó kis bővítés lenne, ha a programunk automatikusan felülírná a bevitt a címet, de az egyszerűség kedvéért itt egy szabályt hozunk létre.)

/**
 * @return array validation rules for model attributes.
 */
public function rules()
{
    // NOTE: you should only define rules for those attributes that
    // will receive user inputs.
    return array(
        array('created, revision', 'numerical', 'integerOnly' => true),
        array('title', 'length', 'max' => 125),
        array('title', 'required', 'message' => 'A cím nem lehet üres!'),
        array('title', 'unique'),
        
        array(
            'title',
            'match',
            'pattern' => '/^[A-Za-z0-9_]+$/',
            'message' => 'Csak számokat, betűket és „_” jelet használhatsz! Hehe.'
        ),
        
        array('body', 'safe'),
    );
}

A használt szabályok elég egyértelműek, de ha valaki jobban bele szeretne mélyülni itt olvashat róluk bővebben: http://yiiframework.com/doc/cookbook/56. Itt azt is megfigyelhetjük, hogy hogyan kell a programunkat magyarul tanítani.

A beforeSave() és save() tagfüggvények használata/felülírása

A modelljeink, amik a programunkban az adatbázis-lekérdezéseket kezelik, mindenféle tagfüggyvényekkel fel vannak fegyverezve, hogy nekünk ne kelljen védőkesztyű nélkül SQL-kódban kotorászni. Ilyen tagfüggvény a save() és hű csatlósa a beforeSave().

A pages táblánkban van egy olyan mező, hogy created, dátum típussal (a példánkban Unix időbélyeget használunk). Ez a mező tartja nyilván, hogy az adott oldal mikor készült/frissült. Ugyan használhatnánk a save() tagfüggvényt arra, hogy ezt az értéket beállítsuk, a példa kedvéért ezt a beforeSave()-vel oldjuk meg. (Az fontos, hogy itt a szülő beforeSave()-jével térjünk vissza!)

public function beforeSave()
{
    // and setting the created date...
    $this->created = time();

    return parent::beforeSave();
}

A save()-nél már kicsit más a helyzet. Először vegyük sorra, hogy mit is szeretnénk a programunktól, amikor elmentünk egy oldalt. A jövőben szeretnénk egy revert opciót, ami annyit jelent, hogy az odalalainkat, ha akarjuk, visszaállíthatjuk egy korábbi állapotba. Ezt többféle módon is meg lehet valósítani, de talán a legegyszerűbb megoldás, ha az oldalakat mindig újként mentjük el, és csak növeljük a revision-t azaz verziószámot.

public function save($validate = true)
{
    if ($this->isNewRecord) {
        // we increase the revision number...
        $this->revision = $this->revision + 1;

        return parent::save($validate);
    } else {
        // by setting `save` to false, it will skip the validation,
        // so we can save the page with the same title
        // also, update is not really an update because every single change
        // will be a "new" page, so we can keep history...
        
        $newpage = new Page();
        $newpage->attributes = $this->attributes;
        $newpage->save(false);
        
        return true;
    }
}

A Yii olyan finomságokkal lát el bennünket, mint például a $this->isNewRecord, ami egyszerűen megmondja nekünk, hogy most egy új rekordról van-e szó, vagy csak felülírunk egyet. Mi persze tudjuk, hogy nekünk arra van szükségünk, hogy minden egyes oldal újként legyen elmentve, tehát ha az oldal nem új, akkor egyszerűen gyártunk egyet, mely a $newpage->attributes = $this->attributes miatt felveszi az eredeti oldal értékeit.

Még annyit érdemes megjegyezni, hogy ha a save() tagfüggvényt false paraméterrel hívjuk meg, akkor az Active Record szabályai nem vonatkoznak az adott rekordra.

Az oldalakat lekérő SQL módosítása, és a hozzá tartozó nézet testreszabása

Ha most néznénk meg az oldalak listáját, azt vennénk észre, hogy programunk az egyes oldalakhoz tartozó összes verziót megmutatja. Ez idáig rendben is van, hiszen minden egyes változatot újként mentettünk el, azonban ez elég zavaró lehet, meg bugyután is néz ki. Ha eddig még nem említettük volna, majd minden vezérlőnek van egy actionIndex()-e, az alapértelmezett tagfüggvény. Jelen esetünkben ez szolgáltatja az oldalak listáját. Keressük is meg a PageController-ben (protected/controllers/PageController.php), és módosítsuk a következők szerint:

/**
 * Lists all models.
 */
public function actionIndex()
{
    $criteria = new CDbCriteria();
    
    $criteria->group = 'title';
    $criteria->order = 'created DESC';

    $dataProvider = new CActiveDataProvider('Page', array(
        'pagination' => array('pageSize' => self::PAGE_SIZE),
        'criteria'   => $criteria,
    ));

    $this->render('index', array('dataProvider' => $dataProvider));
}

SQL-ben valamennyire jártasabbak egyből kiszúrhatják, hogy mi is történik. Itt létrehozunk egy CDBCriteria() osztályt, melynek segítségével különböző SQL paramétereket állíthatunk be, és ezáltal módosíthatjuk a lekérdezéseinket, ami ebben az esetben az oldalak csoportosítását jelenti a cím szerint és rendezést a létrehozás dátuma szerint.

Az alábbi állományokat pedig módosítsuk ízlésünk szerint (illetve az én ízlésem szerint). Ha még emlékszünk, a PageController-t kicsit átalakítottuk, hogy az adott oldal azonosítója helyett a title mező értékével dolgozzon. Ez nagyon szép és jó, de a program többi részét is kicsit át kell alakítanunk. Ilyen például az actionCreate() és actionUpdate() metódusokban használt redirect(), mely azt a célt szolgálja, hogy bizonyos események után böngészőnket egy meghatározott oldalra irányíthassuk.

public function actionCreate()
{
    $model = new Page;
    
    if (isset($_POST['Page'])) {
        $model->attributes = $_POST['Page'];

        if ($model->save())
            $this->redirect(array('view', 'title' => $model->title));
            // $this->redirect(array('view', 'id' => $model->id));
        }

        $this->render('create', array('model' => $model));
}

A következő állományok (protected/views/page/index.php, protected/views/page/view.php) pedig a megjelenítést szolgálják.

<div class="view">
	<?php echo CHtml::link(CHtml::encode($data->title), $this->createUrl('page/view', array('title' => $data->title))); ?>
	(Rev.: <?php echo CHtml::encode($data->revision); ?>)
	<i><?php echo date('Y-m-d H:i', CHtml::encode($data->created)); ?></i>
</div>
<?php
	$this->breadcrumbs = array(
		'Pages' => array('index'),
		$model->title,
	);
?>
<h1><?php echo strtoupper($model->title); ?></h1>
<div>
	<?php echo CHtml::link('Oldalak listája', array('index')); ?>
	<?php echo CHtml::link('Oldal frissítése', array('update', 'id' => $model->id)); ?>
	<?php echo CHtml::linkButton('Oldal törlése', array(
		'submit' => array('delete', 'id' => $model->id),
		'confirm' => 'Biztos, hogy töröljem?'
	)); ?>
</div>
<div><?php echo $model->body; ?></div>

Jajj de jó, már lassan készen is vagyunk. Mondjuk a megjelenített oldal egyáltalán nem úgy néz ki mint egy wiki oldal. A következőkben megnézzük, hogyan lehet külső modulokat behúzni a Yii rendszerbe, pl. egy wiki markup értelmezőt, hogy megússzuk a sok str_replace()-t.

Külső modulok, kiterjesztések használata

Fejlesztés közben gyakran fordul elő olyan helyzet, hogy valaki (lehet, hogy mi magunk) valahol máshol (pl. egy másik keretrendszer alatt) már megoldotta a felmerülő problémát, és szeretnénk a kódot újra használni. Erre Yii barátunk többféle lehetőséget ad. Használhatunk kifejezetten Yiihez szánt kiterjeszéseket vagy a Yiitől teljesen független csomagokat. Ilyen például a Zend Framework RSS komponense. Nekünk jelenleg erre nincs szükségünk, de ha valakit bővebben érdekel, az utánajárhat.

Már nagyon közel vagyunk ahhoz, hogy egy használható kis wikink legyen, már csak maga a wikinyelv-értelmező hiányzik. Persze írhatnánk egyet magunk, de inkább bzzunk benne, hogy valaki más már megírta – és láss csodát, így is van. (Ez a rendszer a WikiCreole nyelvet használja.) Persze nem kötelező ezt használni, de az egyszerűség kedvéért mi ezt választottuk.

Mint bizonyára észrevettük a URL-ből, ennek a csomagnak az ég világon semmi köze nincs a Yii-hez, ez csak egy egyszerű PHP osztály, tehát nekünk a második megközelítés lesz a legjobb. Töltsük is le az összecsomagolt állományt, és másoljuk be a creole.php-t az application/vendors/ mappába (ha nincs ilyen mappánk, csináljunk egyet).

Attól még, hogy az állomány ott heverészik a vendors/ könyvárban, a Yii még nem tudja, hogy mi ezt használni is szeretnénk. Itt jön a Yii::import() statikus tagfüggvény a képbe.

Yii::import('application.vendors.*');

Ami kb. annyit jelent, hogy a továbbiakban Yii rendszerünk ebben a könyvtárban is keresgélni fog PHP osztályok után. (Ezt egyebként beállíthatjuk a protected/config/main.php állományban is az import tömb segítségével.)

Most már csak annyit kell tennünk, hogy a megfelelő nézetben meghívjuk magát a wiki értelmezőt, és átpasszoljuk neki azt a szöveget amit HTML-ként szeretnénk megmutatni a felhasználóknak. Ez a nézet pedig a protected/views/page/view.php alatt található:

$wiki = new creole();
echo $wiki->parse($model->body);

Ja, még valami

Már csak egy apróság van hátra. A rendszer alapból a SiteController-nek adja át a vezérlést, ha valaki meglátogatja az oldalunkat. Ez szép és jó, de sajnos azon az oldalon csak annyi szerepel, „Welcome to My Web Application” és semmilyen link nem utal arra, hogy hol is vannak az oldalaink. Ezt a legegyszerűbben úgy lehet orvosolni, hogy megváltoztatjuk az alapértelmezett vezérlőt, és a SiteController helyett beállítjuk a PageController-t, hiszen az felelős az oldalak kezeléséért. Ja, és ha már úgyis a konfigurációs állományban kell kotorásznunk, akkor be is állíthatjuk a programunk nevét (ami több más helyen is feltűnik, pl. a böngésző címsorában).

return array(
    ...
    'name'              => 'Yiiki – Wiki Yiivel',
    'defaultController' => 'Page',
    ...
);

No, készen is lennénk. Persze nagyon NAGYON sok mindenen lehetne javítani-szépíteni, pl. biztonság / adatbázis védelem, magyarosítás, egységtesztek használata, gyorsítótárazás stb., de talán majd máskor.

Kezdetnek reméljük jó lesz. Sok sikert!

Linkek

 
imehesz arcképe
imehesz
Még a múlt században, '98-99-ben járt Budapesten a SZÁMALK-ra, mint adatbázis programozó (Delphi, Windows stb.), azonban 2000 után megragadta a web, és azóta ez a mániája. Többnyire PHP-ban fejleszt (no meg jQuery-ben). Volt szerencséje több keretrendszerbe is belekóstolni: CakePHP, Zend Framework és mostanában Yii.
1

Apró hiba

bonga · 2010. Május. 31. (H), 07.12
Tetszik a cikk, már vártam Yii cikket a weblaboron :)
Azt hiszem, hogy egy apró logikai hiba maradt a save() függvényben: kimaradt egy tagadás az első feltételből:

public function save($validate = true)  
{  
    if (!$this->isNewRecord) {  
        // we increase the revision number...  
2

Fordított logika

Török Gábor · 2010. Május. 31. (H), 08.55
Szerintem jó:
[…] a $this->isNewRecord, ami egyszerűen megmondja nekünk, hogy most egy új rekordról van-e szó, vagy csak felülírunk egyet. Mi persze tudjuk, hogy nekünk arra van szükségünk, hogy minden egyes oldal újként legyen elmentve, tehát ha az oldal nem új, akkor egyszerűen gyártunk egyet […]
3

Még nem kerek...

bonga · 2010. Május. 31. (H), 12.05
Ok, rendben van, ott a pont, de akkor meg a reviziószám léptetés van a feltétel rossz ágán, nem?
4

Jöhet még Yii

strl · 2010. Május. 31. (H), 13.10
Tetszett a cikk és szívesen olvasnék még bővebben a keretrendszer mélyebb lélektanáról.

Csak így tovább! :)
5

kösz

imehesz · 2010. Jún. 1. (K), 17.31
köszönöm a hozzászólásokat és a tanácsokat.

a cél az (lenne), hogy kisebb (de használható) alkalmazásokon keresztül mutassuk be a Yii rendszert.

az igaz, hogy ha az ember végig meg a demo (blog) programon, akkor elég sok mindent megtanul, de akad olyan eset, ahol az ott bemutatott megoldások nem megfelelőek.

köszi mégegyszer,

--iM