《Yii2 By Example》第2章:創建一個簡單的新聞閱讀器

第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,將會輸出如下結果:

這就是我們期望的!

最後,將itemsListitemDetail聯繫起來,在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.phpcreateUrl的參數修改爲:

<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>

將常用視圖內容分割成多個可複用視圖

有些時候,若干視圖會有部分相同的內容。例如,itemsListitemDetail都需要展示版權信息。

爲了減少工作量,我們需要將這些共有的部分抽取出來,並使用控制器的renderPartial()方法調用(http://www.yiiframework.com/doc-2.0/yii-base-controller.html#renderPartial%28%29-detail)。這個函數和render()有相同的參數;兩者的區別主要是render()會將視圖內容放在佈局中,而renderPartial()只是輸出視圖內容。

例子——在視圖中進行部分渲染

在這個例子中,我們爲itemsListitemDetail創建版權信息。

views\news中創建文件_copyright.php

注意

在Yii2的應用中,可複用視圖文件通常以下劃線開始。

將如下內容寫入文件views\news\_copyright.php文件中:

<div>
    This is text about copyright data for news items
</div>

然後分別在itemsListitemDetail視圖中展示版權信息。

修改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提供了另外一種更爲便捷的方式,添加如下內容到Controlleraction()中:

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]

然後,在Controlleraction()方法中添加一個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可以看到如下內容:

我們可以對上述路徑做如下自定義修改:

  • Controlleractions()的屬性名稱
  • 修改ViewActionviewPrefix屬性,來聲明我們想使用的URL
  • 修改views/controllerName的子文件夾名稱

例如,我們希望在SiteController中使用static來訪問靜態網頁。

通過訪問http://hostname/basic/web/index.php?r=site/static&view=contact查看聯繫人頁面。

那麼可以在SiteControlleractions()添加如下內容:

'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(); ?>

這裏beginBlockendBlock定義了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自定義。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章