第2章 創建一個簡單的新聞閱讀器
本章內容包含:創建第一個控制器,用於展示新聞條目列表和詳情;學習控制器和視圖之間的交互;自定義視圖的布局。
本章結構如下:
- 創建控制器和動作
- 創建用於展示新聞列表的視圖
- 控制器是如何將數據傳送到視圖的
- 例子——創建一個控制器,展示靜態新聞條目列表和詳情
- 將常用視圖內容分割成多個可復用視圖
- 例子——在視圖中進行部分渲染
- 創建靜態頁面
- 在視圖和布局之前共享數據
- 例子——根據URL參數更換布局背景
- 使用動態模塊布局
- 例子——添加展示廣告信息的動態盒
- 使用多個布局
- 例子——使用不同的布局給同一個視圖創建響應式和非響應式布局
創建控制器和動作
為了處理一個請求,第一件要做的事就是創建一個新的控制器。
在創建一個控制器文件時,需要註意如下一些事項:
- 命名空間在最上邊(在基礎應用中一般是app\controllers)
use
引用會使用的類- 控制器類必須繼承
yii\web\Controller
類 - 控制器中的函數,即動作,需要以
action
開頭,並且每一個單詞的首字母大寫
在文件夾
basic\controllers中創建文件
NewsController.php。
然後創建一個和文件名一樣的類,並繼承
yii\web\Controller類,創建一個
actionIndex函數,這個函數對應的請求就是
news/index:
<?php
// 1. specify namespace at the top (in basic application usually app\controllers);
namespace app\controllers;
// 2. specify 'use' path for used class;
use Yii;
use yii\web\Controller;
// 3. controller class must extend yii\web\Controller class;
// This line is equivalent to
// class NewsController extends yii\web\Controller
class NewsController extends Controller
{ // 4. actions are handled from controller functions whose name starts with 'action' and the first letter of each word is uppercase;
public function actionIndex()
{
echo "this is my first controller";
}
}
使用瀏覽器訪問
http://hostname/basic/web/index.php?r=news/index,我們將會看到一個空白頁面,並提示我們this is my first controller.
現在讓我們看看,如果我們忽略了先前提到的需要註意的四件事中的某些時,將會發生什麽錯誤。
命名空間定義了應用中使用到的名稱的等級結構。如果我們忘記使用命名空間,並且
web/index.php中的
YII_DEBUG設置為true,我們將會看到如下錯誤信息:
控制器命名空間缺失
Yii2能非常棒的展示錯誤信息,提示我們檢查是否丟失了命名空間。
use用於指明某個類在應用中的詳細路徑。類都有一個類似這樣的完整路徑
path/to/class/ClassName。如果們在命名空間之後定義添加
use path/to/class/ClassName,那麽我們在使用時只需要引用
ClassName。
但是,如果我們在只是使用
ClassName,但沒有添加
use path/to/class/ClassName,將會出現如下錯誤:
對於初學者來說,這個錯誤非常容易解釋,但是很難發現。
在這個例子中,從截圖中可以看出,在第9行
extends之後使用了
Controller類。但是因為這不是一個完整的
Controller類名,Yii2會嘗試從
app\controllers下去尋找這個
Controller類,但是並沒有找到。
為了解決這個問題,我們必須將第9行的
Controller修改為
yii\web\Controller,或者在文件頂部使用
use path/to/class/ClassName。
控制器通常是
yii\web\Controller的子類。
創建一個展示新聞列表的視圖
現在我們在一個名叫
itemsList的視圖中創建一個簡單的新聞列表。然後從
NewsController中指向這個視圖,我們需要做下面一些事情:
- 在
basic\views
中創建一個名叫news
的文件夾,控制器NewsController
默認在這個文件夾中尋找需要渲染的視圖。 - 在
basci\views\news
中創建文件itemsList.php
打開文件
basci\views\news\itemsList.php,創建一個數據數組和一個展示數據的表格:
<?php $newsList = [ [ 'title' => 'First World War', 'date' => '1914-07-28' ], [ 'title' => 'Second World War', 'date' => '1939-09-01' ], [ 'title' => 'First man on the moon', 'date' => '1969-07-20' ] ]; ?> <table> <tr> <th>Title</th> <th>Date</th> </tr> <?php foreach($newsList as $item) { ?> <tr> <td><?php echo $item['title'] ?></td> <td><?php echo $item['date'] ?></td> </tr> <?php } ?> </table>
然後我們需要在控制器中創建一個
actionItemsList的動作函數,可以通過
http://hostname/basic/web/index.php?r=news/items-list訪問到它。
建議
下載示例代碼
你可以在http://www.packtpub.com上下載你購買的書的示例代碼。如果你在別的地方買了這本書,你可以去http://www.packtpub.com/support這裏尋求幫助。
註意
註意路徑、控制器和動作的名字:
- 這個動作的路徑是
news/items-list
(小寫,單詞之間用短橫線連接); - 控制器類的名稱是
NewsController
(單詞首字母大寫,最後跟著Controller); NewsController
中的動作函數是actionItemsList
(其中action
是一個固定前綴,沒有路徑中的短橫線,且每個單詞的首字母大寫);
NewsController中的函數如下所示:
public function actionItemsList() { return $this->render('itemsList'); }
其中
render()方法屬於
\yii\web\Controller,用於展示第一個參數所代表的視圖,框架查找這個視圖的時候,會給參數添加上
.php的後綴,並在
basic\views\news文件夾中尋找。
現在我們可以訪問
http://hostname/basic/web/index.php?r=news/items-list看到效果啦!
控制器是如何將數據傳送到視圖的
我們已經看到了如何展示視圖內容。但是,視圖的作用只是展示數據,而不是操縱數據。因此,所有的數據都應該在控制器動作中準備好,然後才傳入到視圖中。
控制器動作中的
render()方法還有第二個參數,它是一個向量,該向量的一系列key是變量的名稱,value是這些變量的值,這些變量可以在視圖中使用。
現在我們修改之前的代碼,將
itemsList需要的數據在控制器中準備。
下邊是控制器中
actionItemsList()的內容:
public function actionItemsList() { $newsList = [ [ 'title' => 'First World War', 'date' => '1914-07-28' ], [ 'title' => 'Second World War', 'date' => '1939-09-01' ], [ 'title' => 'First man on the moon', 'date' => '1969-07-20' ] ]; return $this->render('itemsList', ['newsList' => $newsList]); }
文件
views/news/itemsList.php中的代碼是:
<?php // $newsList is from actionItemsList ?> <table> <tr> <th>Title</th> <th>Date</th> </tr> <?php foreach($newsList as $item) { ?> <tr> <th><?php echo $item['title'] ?></th> <th><?php echo $item['date'] ?></th> </tr> <?php } ?> </table>
這樣我們就將控制器和視圖分開了。
例子——創建一個控制器,使用bootstrap模板展示靜態新聞條目列表和詳情
接下來的目標是在另外一個頁面中完成新聞新聞閱讀器的單條新聞的詳情展示部分。
因為列表和詳情使用的是同一套數據,我們將
$newsList數據從控制器動作中拿出來,進而可以用在別的動作中。
在
NewsController中,添加如下的代碼:
public function dataItems() { $newsList = [ [ 'title' => 'First World War', 'date' => '1914-07-28' ], [ 'title' => 'Second World War', 'date' => '1939-09-01' ], [ 'title' => 'First man on the moon', 'date' => '1969-07-20' ] ]; return $newsList; } public function actionItemsList() { $newsList = $this->dataItems(); return $this->render('itemsList', ['newsList' => $newsList]); }
然後在
NewsController中創建一個新的動作
actionItemDetail,用於處理新聞條目詳情的請求。這個函數有一個參數,指明需要展示
$newsList哪條新聞,例如通過標題。
public function actionItemDetail($title) { $newsList = $this->dataItems(); $item = null; foreach($newsList as $n) { if($title == $n['title']) $item = $n; } return $this->render('itemDetail', ['item' => $item]); }
接下來在
views\news中創建一個新的視圖文件
itemDetail.php:
<?php // $item is from actionItemDetail ?> <h2>News Item Detail</h2> <br /> Title: <b><?php echo $item['title'] ?></b> <br /> Date: <b><?php echo $item['date'] ?></b>
如果訪問
http://hostname/basic/web/index.php?r=news/item-detail,沒有指出標題參數,那麽將會返回如下錯誤:
頁面展示了一個錯誤信息,提示我們缺少標題參數。
修正URL,訪問
http://hostname/basic/web/index.php?r=news/item-detail&title=First%20World%20War,將會輸出如下結果:
這就是我們期望的!
最後,將
itemsList和
itemDetail聯系起來,在
views\news\itemsList.php中,我們做如下修改:
<?php // $newsList is from actionItemsList ?> <table> <tr> <th>Title</th> <th>Date</th> </tr> <?php foreach($newsList as $item) { ?> <tr> <th><a href="<?php echo Yii::$app->urlManager->createUrl(['news/item-detail' , 'title' => $item['title']]) ?>"><?php echo $item['title'] ?></a></th> <th><?php echo $item['date'] ?></th> </tr> <?php } ?> </table>
使用組件
urlManager可以創建一個鏈接,具體是使用
createUrl()方法。
createUrl()的參數是一個向量,其中包含了路徑和需要通過URL傳遞的參數。關於這個方法更多的介紹,可以去這裏查看http://www.yiiframework.com/doc-2.0/yii-web-urlmanager.html#createUrl%28%29-detail。
在這裏例子中,請求的路徑是
news\item-detail,並且傳遞了參數
title。
註意
可以用於內置格式化組件格式化日期。例如,為了以d/m/Y的格式展示日期,
echo \Yii::$app->formatter->asDatetime('2010-01-23', "php:d/m/Y");將會輸出
23/01/2010。
建議為每條新聞指定一個唯一的編號,這裏我們為數據添加一個
id項。
下面是
NewsController的內容:
public function dataItems() { $newsList = [ [ 'id' => 1, 'title' => 'First World War', 'date' => '1914-07-28' ], [ 'id' => 2, 'title' => 'Second World War', 'date' => '1939-09-01' ], [ 'id' => 3, 'title' => 'First man on the moon', 'date' => '1969-07-20' ] ]; return $newsList; } public function actionItemsList() { $newsList = $this->dataItems(); return $this->render('itemsList', ['newsList' => $newsList]); } public function actionItemDetail($id) { $newsList = $this->dataItems(); $item = null; foreach($newsList as $n) { if($id == $n['id']) $item = $n; } return $this->render('itemDetail', ['item' => $item]); }
然後將
views\news\itemsList.php中
createUrl的參數修改為:
<table> <tr> <th>Title</th> <th>Date</th> </tr> <?php foreach($newsList as $item) { ?> <tr> <th><a href="<?php echo Yii::$app->urlManager->createUrl(['news/item-detail' , 'id' => $item['id']]) ?>"><?php echo $item['title'] ?></a></th> <th><?php echo Yii::$app->formatter->asDatetime($item['date'], "php:d/m/Y"); ?></th> </tr> <?php } ?> </table>
將常用視圖內容分割成多個可復用視圖
有些時候,若幹視圖會有部分相同的內容。例如,
itemsList和
itemDetail都需要展示版權信息。
為了減少工作量,我們需要將這些共有的部分抽取出來,並使用控制器的
renderPartial()方法調用(http://www.yiiframework.com/doc-2.0/yii-base-controller.html#renderPartial%28%29-detail)。這個函數和
render()有相同的參數;兩者的區別主要是render()會將視圖內容放在布局中,而renderPartial()只是輸出視圖內容。
例子——在視圖中進行部分渲染
在這個例子中,我們為
itemsList和
itemDetail創建版權信息。
在
views\news中創建文件
_copyright.php。
註意
在Yii2的應用中,可復用視圖文件通常以下劃線開始。
將如下內容寫入文件
views\news\_copyright.php文件中:
<div> This is text about copyright data for news items </div>
然後分別在
itemsList和
itemDetail視圖中展示版權信息。
修改
views\news\itemsList.php的內容為:
<?php echo $this->context->renderPartial('_copyright'); ?> <table> <tr> <th>Title</th> <th>Date</th> </tr> <?php foreach($newsList as $item) { ?> <tr> <th><a href="<?php echo Yii::$app->urlManager->createUrl(['news/item-detail' , 'id' => $item['id']]) ?>"> <?php echo $item['title'] ?> </a></th> <th><?php echo Yii::$app->formatter->asDatetime($item['date'], 'php:d/m/Y'); ?></th> </tr> <?php } ?> </table>
修改
views\news\itemsDetail.php的內容為:
<?php // $item is from actionItemDetail ?> <?php echo $this->context->renderPartial('_copyright'); ?> <h2>News Item Detail</h2> <br /> Title: <b><?php echo $item['title'] ?></b> <br /> Date: <b><?php echo $item['date'] ?></b>
在這兩個文件中我們都添加了下面的內容:
<?php echo $this->context->renderPartial('_copyright'); ?>
註意
註意!因為
renderPartial()是
Controller的方法,
$this代指視圖文件中的
View類,所以為了使用
renderPartial()必須使用上下文成員context,它是
View對象中的
Controller對象。
創建靜態網頁
所有的網站都包含靜態網頁,他們的內容是靜態的。
為了以更常見的方式創建,我們需要做如下工作:
- 在
Controller
中創建一個函數(action
) - 創建一個靜態內容視圖
將如下內容放在
Controller中:
public function actionInfo() { return $this->render('info'); }
然後創建一個視圖文件
views/controller/action-name.php。這個過程比較長,並且比較冗余。
Yii2提供了另外一種更為便捷的方式,添加如下內容到
Controller的
action()中:
public function actions() { return [ 'pages' => [ 'class' => 'yii\web\ViewAction', ], ]; }
然後我們就可以將靜態內容網頁放在
views/controllerName/pages中了。
最後,可以通過訪問
http://hostname/basic/web/index.php?r=controllerName/pages&view=name_of_view來查看相關的靜態網頁。
例子——添加一個聯系人頁
在了解了如何創建靜態網頁以後,我們來創建一個聯系人頁。
在
views/site/pages/contact.php中添加如下內容:
To contact us, please write to [email protected]
然後,在
Controller的
action()方法中添加一個
page屬性,這裏我們在
SiteController中做如下修改:
原來的內容:
public function actions() { return [ 'error' => [ 'class' => 'yii\web\ErrorAction', ], 'captcha' => [ 'class' => 'yii\captcha\CaptchaAction', 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, ], ]; }
修改後:
public function actions() { return [ 'error' => [ 'class' => 'yii\web\ErrorAction', ], 'captcha' => [ 'class' => 'yii\captcha\CaptchaAction', 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, ], 'pages' => [ 'class' => 'yii\web\ViewAction', ], ]; }
設置好以後,所有對
site/pages的請求都會使用
ViewAction類,具體就是方法相對應的靜態視圖。
訪問網址
http://hostname/basic/web/index.php?r=site/pages&view=contact可以看到如下內容:
我們可以對上述路徑做如下自定義修改:
Controller
中actions()
的屬性名稱- 修改
ViewAction
的viewPrefix
屬性,來聲明我們想使用的URL - 修改
views/controllerName
的子文件夾名稱
例如,我們希望在
SiteController中使用
static來訪問靜態網頁。
通過訪問
http://hostname/basic/web/index.php?r=site/static&view=contact查看聯系人頁面。
那麽可以在
SiteController中
actions()添加如下內容:
'static' => [ 'class' => 'yii\web\ViewAction', 'viewPrefix' => 'static' ],
同時我們需要添加文件夾
view/site/static,這樣我們就可以訪問
http://hostname/basic/web/index.php?r=site/static&view=contact頁面了。
在視圖和布局之前共享數據
在視圖與布局之間共享數據,Yii2提供了一種標準的方法,即通過視圖控件的
params屬性。
註意
這是一個標準的方法,因為
params存在於所有的視圖中。
params屬性是一個數組,沒有任何使用限制。
例如我們希望保存面包屑的數據,用於展示在導航路徑中。
打開主視圖
views/layouts/main.php;找到有關面包屑的代碼:
<div class="container"> <?= Breadcrumbs::widget([ 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], ]) ?> </div>
在文件
views/site/index.php中添加如下代碼:
$this->params['breadcrumbs'][] = 'My website';
註意
因為這是在視圖文件中,
$this代表視圖控件。
訪問
http://hostname/basic/web/index.php?r=site/index可以看到如下圖所示的面包屑導航:
例子——根據URL參數更換布局背景
視圖和布局之間進行通信的另外一個例子是,基於URL參數改變布局背景。
通過向路徑為
site/index的URL傳遞參數
bckg來修改背景。
因此,我們必須在代碼
views/site/index.php中添加如下內容:
<?php
$backgroundColor = isset($_REQUEST['bckg'])?$_REQUEST['bckg']:'#FFFFFF';
$this->params['background_color'] = $backgroundColor;
如果沒有傳遞參數
bckg,這行代碼會將
$backgroundColor設置為
#FFFFFF(白色);否則會被設置為指定的值。
打開
views/layout/main.php,在
body標簽中,根據
params['background_color']修改它的style。
<?php $backgroundColor = isset($this->params['background_color'])?$this->params['background_color']:'#FFFFFF'; ?> <body style="background-color:<?php echo $backgroundColor ?>">
訪問
http://hostname/basic/web/index.php?r=site/index&bckg=yellow可以看到背景是黃色的,訪問
http://hostname/basic/web/index.php?r=site/index&bckg=#FF0000可以看到背景是紅色的。
註意
在這個例子中,我們只在
views/site/index.php中給
params添加了
background_color參數。如果我們不在布局文件中檢查
background_color屬性是否被設置了,那麽有可能會收到一個錯誤。
使用動態模塊布局
params參數用於視圖和布局之前的通信,這一般只適用於簡單的情況,如果遇到比較復雜的情況時,我們需要傳遞HTML塊。
例如,布局中的廣告區(使用模板中的左列或者右列),它可以根據需要展示的視圖來改變。
在這個條件下,我們需要將HTML整塊從視圖傳遞到布局中。
為了這個目的,框架提供了Block,我們可以在這裏定義需要從視圖傳遞到布局的數據。
使用Blocks意味著定義在一個視圖中定義它,並在另一個視圖中進行展示,通常是在布局中。
我們可以按如下方式定義
Block:
<?php $this->beginBlock('block1'); ?> ...content of block1... $this->endBlock(); ?>
這裏
beginBlock和
endBlock定義了
block1的開始和結束。其中的內容被保存到的視圖控件中的
block1的屬性中。
我們可以使用
$view>blocks[$blockID]在任意一個視圖中獲取這個塊,包含布局。
為了在布局視圖中使用這個塊,可以使用如下代碼:
<?php if(isset($this->blocks['block1']) { ?> <?php echo $this->blocks['block1'] ?> <?php } else { ?> … default content if missing block1 attribute <?php } ?>
例子——添加展示廣告信息的動態盒
在這個例子中,我們會展示一個廣告信息塊,且數據是從視圖傳遞過來的。
首先添加一個展示數據的塊。
打開
views/layouts/main.php並修改做如下修改:
<div class="container"> <?= Breadcrumbs::widget([ 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], ]) ?> <div class="well"> This is content for blockADV from view <br /> <?php if(isset($this->blocks['blockADV'])) { ?> <?php echo $this->blocks['blockADV']; ?> <?php } else { ?> <i>No content available</i> <?php } ?> </div> <?= $content ?> </div>
然後在
NewsController中創建一個新的動作,
advTest。
創建文件
views/news/advTest.php:
<span> This is a test where we display an adv box in layout view </span> <?php $this->beginBlock('blockADV'); ?> <b>Buy this fantastic book!</b> <?php $this->endBlock(); ?>
我們可以在這個block中創建任何內容。這裏我們添加了一些文本。
註意
在視圖中創建block的位置是不重要的。
然後,打開
NewsController添加如下內容:
public function actionAdvTest() { return $this->render('advTest'); }
訪問
http://hostname/basic/web/index.php?r=news/adv-test將會看到如下結果:
在其它頁面上將會看到
no content available。
使用多個布局
在創建一個網站或者一個網站應用時,通常需要在不同的布局中渲染不同的視圖。例如,本章中制作的新聞列表和詳情。
布局是由
Controller的
$layout屬性管理的;
main是這個屬性的缺省值。
修改這個屬性的值,就可以改變布局。
關於
$layout屬性有如下一些規則:
- 路徑別名(例如,
@app/views/layouts/main
)。 - 絕對路徑(例如,
/main
),以一個斜線開始。布局文件將會在應用布局路徑中尋找,這個路徑的缺省值是@app/views/layouts
。 - 相對路徑(例如,
main
),將會在當前模塊的布局路徑中尋找,該路徑的缺省值是當前模塊的views/layouts
。 - 布爾值false表示沒有使用布局文件。
註意
如果布局值不含有文件擴展名,它將會使用缺省值
.php。
例子——使用不同的布局給同一個視圖創建響應式和非響應式布局
在這個例子中,我們會在
NewsController中創建一個新的動作,它根據URL傳遞的參數修改布局。
public function actionResponsiveContentTest() { $responsive = Yii::$app->request->get('responsive', 0); if($responsive) { $this->layout = 'responsive'; } else { $this->layout = 'main'; } return $this->render('responsiveContentTest', ['responsive' => $responsive]); }
在這個動作中,我們根據URL中的參數
responsive設置變量
$responsive,缺省值是0。
然後根據
$responsive動態修改
Controller的
$layout,並將這個變量傳遞給視圖。
創建一個新的視圖
views/news/responsiveContentTest.php:
<?php if($responsive) { ?> This layout contains responsive content <?php } else { ?> This layout does not contain responsive content <?php } ?>
根據
$responsive展示不同的文本塊。
最後,將
view/layouts/main.php復制為
views/layouts/responsive.php,並在新文件中作如下修改:
<div class="container"> in <div class="container-fluid" style="padding-top:60px">
這個改變將div容器修改為響應式的。
如果訪問
http://hostname/basic/web/index.php?r=news/responsive-content-test,我們將會看到一個固定的布局。如果我們將
responsive的參數設置為1,
http://hostname/basic/web/index.php?r=news/responsive-content-test&responsive=1,我們將會看到一個寬屏布局。
總結
在本章中,在了解了Yii2應用的結構以後,我們創建我們的第一個控制器和相關的視圖。我已經看到的動態和靜態視圖,我們已經學習了如何在布局中渲染視圖,並從控制器中傳遞數據到視圖中,以及如何復用視圖。
最後,我們操作了布局,並根據條件修改它。
在下一章中,我們將會用更好的方式展示URL,這對搜索引擎優化(SEO)非常重要。然後,我們將會學習如何創建一個自定義URL句柄來管理任何需要的URL自定義。
Tags: 閱讀器 usually action 控制器 文件夾
文章來源: