有了前面“Hello,World”的例子和對 Yii Framework Web 應(yīng)用基礎(chǔ)的介紹,可以開始介紹一個簡單而相對而有比較完整的Web應(yīng)用-Hangman(猜單詞游戲),這個例子是隨 Yii 開發(fā)包發(fā)布的。通過這個例子可以了解開發(fā) Yii 應(yīng)用的基本步驟.
說起“Hangman”,讓我想起 80 年代末期高中時在 CPC464 計(jì)算機(jī)上完過的“猜單詞游戲”-Hangman,每猜錯一次,就把一個小人離絞刑架前進(jìn)一步。當(dāng)時 DOS 才剛剛出來:-)。
開發(fā)一個 Web 應(yīng)用,首先是進(jìn)行需求分析,這個不在本教程之內(nèi),但為完整起見,還是把“猜單詞游戲”的規(guī)則列在下面:
猜單詞游戲(英文:Hangman,“上吊的人”之意)是一個雙人游戲。一位玩家想一個字,另一位嘗試猜該玩家所想的字中的每一個字母。
要猜的字以一列橫線表示,讓玩家知道該字有多少個字母。如果猜字的玩家猜中其中一個字母,另一位便須于該字母出現(xiàn)的所有位置上寫上該字母。如果猜的字母沒有于該字中出現(xiàn),另一位玩家便會畫吊頸公仔的其中一筆。游戲會在以下情況結(jié)束:
“我要 t 字?!薄坝校?在第八和第十一位。”
今天給出的例子就不畫出“上吊人”了,猜對了顯示“You Win”,猜錯了顯示“You Lose”。 因此我們可以設(shè)計(jì)四個頁面:
http://wiki.jikexueyuan.com/project/yii-development-tutorial/images/4.1.jpg" alt="picture4.1" />
這四個頁面對應(yīng)到 Yii Framework 為 四個 View,可以分別取名為 play, guess, win,lose ,每個頁面都顯示了“Hangman Game”的標(biāo)題,因此可以設(shè)計(jì)一個”MasterPage”,在 Yii 中成為 Layout 布局的模板以供四個 View 共享。Yii 應(yīng)用采用了 MVC 設(shè)計(jì)模式,因此我們可以為四個 View 設(shè)計(jì)一個 Controller–>GameController.
前面的教程說過 Yii 應(yīng)用使用缺省的目錄結(jié)構(gòu)來存放應(yīng)用的不同部分,可以使用 Yii提供的工具來參加一個缺省的項(xiàng)目目錄。不過我個人還是比較喜歡自己創(chuàng)建各個目錄,因此根據(jù)上面的需求和界面設(shè)計(jì),可以創(chuàng)建項(xiàng)目的目錄結(jié)構(gòu)如下:
http://wiki.jikexueyuan.com/project/yii-development-tutorial/images/4.2.jpg" alt="picture4.2" />
1.首先來看看配置文件 protected/config/main.php
return array(
'name'=>'Hangman Game',
'defaultController'=>'game',
'components'=>array(
'urlManager'=>array(
'urlFormat'=>'path',
'rules'=>array(
'game/guess/<g:\w>'=>'game/guess',
),
),
),
);
CWebApplication 應(yīng)用的所有可寫的屬性都可以通過配置文件來定義,我們看到配置文件定義了應(yīng)用的名稱為”Hangman Game” ,然后修改 Web 應(yīng)用缺省的 Controller 名字為 game 對應(yīng)到 GameController, 如果沒有重新定義 defaultController,則缺省的 Controller 名字為 SiteController,這樣對于的 View 就要存放到 protected/views/site 目錄下。另外這個 Yii 應(yīng)用打開了 urlManager 組件,這個組件的功能就在后面介紹,主要是用來定義用戶可以訪問的 URL 的格式(路由格式)。
2.有了這個配置文件,就可以在入口腳本中使用它,每個 Yii 應(yīng)用的入口腳本 index.php 都是大同小異的,大部分情況下都是 Copy & Paste :-)
$yii=dirname(__FILE__).'/../../framework/yii.php';
$config=dirname(__FILE__).'/protected/config/main.php';
require_once($yii);
Yii::createWebApplication($config)->run();
3.然后定義 View 使用的布局文件 protected/views/layout/main.php main.php 為缺省的布局模板,應(yīng)用可以修改 View 使用的布局,本例就是要缺省的布局名稱main.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Hangman Game</title>
</head>
<body>
<h1>Hangman Game</h1>
<?php echo $content; ?>
</body>
</html>
布局基本上就是 HTML 文件,其中 作為 view 的 placeholder ,也就是在顯示具體的 View 時,比如 play.php 就用 play.php 的內(nèi)容來替代 $content。從而實(shí)現(xiàn)了類似“MasterPage” 的功能。
4.下面就可以逐一定義四個 View,這里就不一一列出了,以 play.php 為例:
<p>This is the game of Hangman.
You must guess a word, a letter at a time.
If you make too many mistakes, you lose the game!</p>
<?php echo CHtml::beginForm(); ?>
<?php echo CHtml::radioButtonList('level', null, $levels); ?>
<br/>
<?php echo CHtml::submitButton('Play!'); ?>
<?php if($error): ?>
<span style="color:red">You must choose a difficulty level!</span>
<?php endif; ?>
<?php echo CHtml::endForm(); ?>
可以看到基本上也是 HTML ,其中 CHtml 為 Yii 框架支持的一個輔助類,用來幫助生成 HTML 代碼。 Hangman 比較簡單,因此沒有使用單獨(dú)的 Model,而是通過 render 推送的方式傳入?yún)?shù)。
需通過傳遞視圖的名稱調(diào)用 CController::render()。這個方法將在 protected/views/ControllerID 目錄下尋找對應(yīng)的視圖文件.
在視圖腳本內(nèi)部,我們可以通過 $this 來訪問控制器實(shí)例.我們可以在視圖里以 $this->propertyName 的方式 拉取 控制器的任何屬性.
我們也可以用以下推送的方式傳遞數(shù)據(jù)到視圖里:
$this->render('edit', array(
'var1'=>$value1,
'var2'=>$value2,
));
在以上的方式中, render() 方法將提取數(shù)組的第二個參數(shù)到變量里.其產(chǎn)生的結(jié)果是,在視圖腳本里,我們可以直接訪問變量 $var1 和 $var2.
5.定義好布局和 View 之后,就可以寫 GameController 了,
class GameController extends CController
{
/**
* @var string sets the default action to be 'play'
*/
public $defaultAction='play';
/**
* The 'play' action.
* In this action, users are asked to choose a difficulty level
* of the game.
*/
public function actionPlay()
{
...
}
/**
* The 'guess' action.
* This action is invoked each time when the user makes a guess.
*/
public function actionGuess()
{
...
}
/**
* The 'guess' action.
* This action is invoked when the user gives up the game.
*/
public function actionGiveup()
{
...
}
...
}
一般情況下 Controller 缺省的 action 為 index ,可以通過$defaultAction 修改缺省的 Action,本例修改為 play. 因此如果本例的 url 為 http://127.0.0.1:8888/yii/demos/hangman/ 那么使用 http://127.0.0.1:8888/yii/demos/hangman/index.php 和使用 http://127.0.0.1:8888/yii/demos/hangman/index.php?game/play 的效果是一樣的。缺省的 Controller 為 GameController,GameController 缺省的 action 為 play.
Action (動作),動作可以被定義為一個以 action 單詞作為前綴命名的方法。Hangman 定義了三個 action ,actionPlay ,actionGuess, actionGiveup ,GameController 其它方法和屬性和生成單詞,判斷是否猜對等為具體的游戲邏輯和 Yii 框架關(guān)系不大,就不介紹了。
6.首先看看缺省的 playAction ,這是用戶調(diào)用的缺省方法,也就是說當(dāng)用戶組地址欄輸入 http://127.0.0.1:8888/yii/demos/hangman/index.php (或http://127.0.0.1:8888/yii/demos/hangman/index.php?game/play)所調(diào)用的 Action。
public function actionPlay()
{
static $levels=array(
'10'=>'Easy game; you are allowed 10 misses.',
'5'=>'Medium game; you are allowed 5 misses.',
'3'=>'Hard game; you are allowed 3 misses.',
);
// if a difficulty level is correctly chosen
if(isset($_POST['level'])
&& isset($levels[$_POST['level']]))
{
$this->word=$this->generateWord();
$this->guessWord=str_repeat('_',strlen($this->word));
$this->level=$_POST['level'];
$this->misses=0;
$this->setPageState('guessed',null);
// show the guess page
$this->render('guess');
}
else
{
$params=array(
'levels'=>$levels,
// if this is a POST request,
//it means the level is not chosen
'error'=>Yii::app()->request->isPostRequest,
);
// show the difficulty level page
$this->render('play',$params);
}
}
這個方法定義了游戲的三個難度等級$levels, 有兩個分支,如果沒有選擇難易等級,則調(diào)用$this->render(‘play’,$params),顯示 Play 頁面,就$params (Array)推送到對應(yīng)的 View ,protected/views/play.php,參考上面 View 的定義:
<?php echo CHtml::radioButtonList('level', null, $levels); ?>
View 使用 Radiobutton來顯示 $levels 定義的列表。
如果用戶選擇了難易等級,在把 Level,單詞等存放到 GameController 所定義的屬性中,如 word,level 等。GameController 拍手與 CController 也是 CComponent 的子類,CComponent 支持了類似 C#,Java 的屬性功能。具體后面再介紹。 然后調(diào)用$this->render(‘guess’); 顯示 Guess 頁面。
Guess 頁面 guess.php 定義如下:
<h2>Please make a guess</h2>
<h3 style="letter-spacing: 4px;">
<?php echo $this->guessWord; ?></h3>
<p>You have made
<?php echo $this->misses; ?>
bad guesses out of a maximum of
<?php echo $this->level; ?>.</p>
<?php echo CHtml::statefulForm(); ?>
<p>Guess:
<?php
for($i=ord('A');$i<=ord('Z');++$i)
{
if(!$this->isGuessed(chr($i)))
echo "\n".CHtml::linkButton(chr($i),
array('submit'=>array('guess','g'=>chr($i))));
}
?>
</p>
<p><?php echo CHtml::linkButton('Give up?',
array('submit'=>array('giveup'))); ?></p>
</form>
在 View 中可以直接通過 $this 來訪問對應(yīng)的 Controller 實(shí)例對象的方法和屬性。 如 $this->guessWord,$this->isGuessed(chr($i))等。 其中點(diǎn)擊 26 個字母觸發(fā) guessAction (array(‘submit’=>array(‘guess’,’g’=>chr($i))))).
7.下面為 guessAction 的定義
public function actionGuess()
{
// check to see if the letter is guessed correctly
if(isset($_GET['g'][0]) && ($result=$this->guess($_GET['g'][0]))!==null)
$this->render($result ? 'win' : 'lose');
else // the letter is guessed correctly, but not win yet
{
$guessed=$this->getPageState('guessed',array());
$guessed[$_GET['g'][0]]=true;
$this->setPageState('guessed',$guessed,array());
$this->render('guess');
}
}
其中參數(shù) ‘g’由 guess 頁面提交是傳入, 如果單詞全部猜對在顯示”You win” 或用完所有次數(shù)猜錯顯示“You lose” , $this->render($result ? ‘win’ : ‘lose’), 如果還有機(jī)會猜還是回到 guess 頁面 $this->render(‘guess’);
8.在 Guess 頁面上還有一個“Give up” 按鈕,用戶點(diǎn)擊則觸發(fā) giveupAction.這個方法比較簡單,直接顯示 lose 頁面
public function actionGiveup()
{
$this->render('lose');
}
至此 Hangman 游戲基本就完成了。游戲雖然簡單,但說明了使用 Yii 開發(fā)應(yīng)用的基本流程,下面給出 Yii 開發(fā)文檔給出的開發(fā)流程,Hangman 比較簡單,沒有用到數(shù)據(jù)庫和國際化等。
此處的開發(fā)流程假設(shè)我們已經(jīng)完成了對應(yīng)用的需求分析和必要的設(shè)計(jì)分析。