從 Yii 1.1升級到 Yii2


從 Yii 1.1 升級

2.0 版框架是完全重寫的,在 1.1 和 2.0 兩個版本之間存在相當多差異。因此從 1.1 版升級並不像小版本間的跨越那麼簡單,通過本指南你將會了解兩個版本間主要的不同之處。
 
如果你之前沒有用過 Yii 1.1,可以跳過本章,直接從"[入門篇](start-installation.md)"開始讀起。
 
請注意,Yii 2.0 引入了很多本章並沒有涉及到的新功能。強烈建議你通讀整部權威指南來了解所有新特性。這樣有可能會發現一些以前你要自己開發的功能,而現在已經被包含在核心程式碼中了。
 

安裝

Yii 2.0 完全擁抱 [Composer](https://getcomposer.org/),它是PHP中的一個依賴管理工具。核心框架以及擴充套件的安裝都通過 Composer 來處理。想要了解更多如何安裝 Yii 2.0 請參閱本指南的 [安裝 Yii](start-installation.md) 章節。如果你想建立新擴充套件,或者把你已有的 Yii 1.1 的擴充套件改寫成相容 2.0 的版本,你可以參考 [建立擴充套件](extend-creating-extensions.md) 章節。
 

PHP 需求

Yii 2.0 需要 PHP 5.4 或更高版本,該版本相對於 Yii 1.1 所需求的 PHP 5.2 而言有巨大的改進。因此在語言層面上有很多的值得注意的不同之處。下面是 PHP 層的主要變化彙總:
 
- [名稱空間](http://php.net/manual/zh/language.namespaces.php)
- [匿名函式](http://php.net/manual/zh/functions.anonymous.php)
- 陣列短語法 `[...元素...]` 用於取代 `array(...元素...)`
- 檢視檔案中的短格式 echo 標籤 `<?=`,自 PHP 5.4 起總會被識別並且合法,無論 short_open_tag 的設定是什麼,可以安全使用。
- [SPL 類和介面](http://php.net/manual/zh/book.spl.php)
- [延遲靜態系結](http://php.net/manual/zh/language.oop5.late-static-bindings.php)
- [日期和時間](http://php.net/manual/zh/book.datetime.php)
- [Traits](http://php.net/manual/zh/language.oop5.traits.php)
- [intl](http://php.net/manual/zh/book.intl.php) Yii 2.0 使用 PHP 擴充套件 `intl` 來支援國際化的相關功能。
 
 

名稱空間

Yii 2.0 裡最明顯的改動就數名稱空間的使用了。幾乎每一個核心類都引入了名稱空間,比如 `yii\web\Request`。1.1 版用於類名前的字母 “C” 已經不再使用。當前的命名規範與目錄結構相吻合。例如,`yii\web\Request` 就表明對應的類檔案是 Yii 框架檔案夾下的 `web/Request.php` 檔案。
 
(有了 Yii 的類自動載入器,你可以直接使用全部核心類而不需要顯式包含具體檔案。)
 

元件(Component)與物件(Object)

Yii 2.0 把 1.1 裡的 `CComponent` 類拆分成了兩個類:[[yii\base\Object]] 和 [[yii\base\Component]]。[[yii\base\Object|Object]] 類是一個輕量級的基礎類別,你可以通過 getters 和 setters 來定義 [object 的屬性](concept-properties.md)。[[yii\base\Component|Component]] 類繼承自 [[yii\base\Object|Object]],同時進一步支援 [事件](concept-events.md) 和 [行為](concept-behaviors.md)。
如果你不需要用到事件或行為,應該考慮使用 [[yii\base\Object|Object]] 類作為基礎類別。這通常是表示基本資料結構的類。
 

物件的組態

[[yii\base\Object|Object]] 類引入了一種統一物件組態的方法。所有 [[yii\base\Object|Object]] 的子類都應該用以下方法宣告它的構造方法(如果需要的話),以正確組態它自身:
 
class MyClass extends \yii\base\Object
{
    public function __construct($param1, $param2, $config = [])
    {
        // ... 組態生效前的初始化過程

        parent::__construct($config);
    }

    public function init()
    {
        parent::init();

        // ...組態生效後的初始化過程
    }
}
在上面的例子裡,構造方法的最後一個引數必須輸入一個組態陣列,包含一系列用於在方法結尾初始化相關屬性的鍵值對。你可以重寫 [[yii\base\Object::init()|init()]] 方法來執行一些需要在組態生效後進行的初始化工作。
 
你可以通過遵循以下約定俗成的編碼習慣,來使用組態陣列建立並組態新的物件:
 
$object = Yii::createObject([
    'class' => 'MyClass',
    'property1' => 'abc',
    'property2' => 'cde',
], [$param1, $param2]);
 
更多有關組態的細節可以在[組態](concept-configurations.md)章節找到。
 

事件(Event)

在 Yii 1 中,通常通過定義 `on` 開頭的方法(例如 `onBeforeSave`)來建立事件。而在 Yii 2 中,你可以使用任意的事件名了。同時通過呼叫 [[yii\base\Component::trigger()|trigger()]] 方法來觸發相關事件:
 
$event = new \yii\base\Event;
$component->trigger($eventName, $event);
 
要給事件附加一個事件控制代碼(Event Handler 或者叫事件處理器),需要使用 [[yii\base\Component::on()|on()]] 方法:
 
$component->on($eventName, $handler);
// 要解除相關控制代碼,使用 off 方法:
// $component->off($eventName, $handler);
 
事件功能還有更多增強之處。要了解它們,請檢視[事件](concept-events.md)章節。
 

路徑別名(Path Alias)

Yii 2.0 將路徑別名的應用擴大至檔案/目錄路徑和 URL。Yii 2.0 中路徑別名必須以 `@` 符號開頭,以區別於普通檔案目錄路徑或 URL。例如 `@yii` 就是指向 Yii 安裝目錄的別名。絕大多數 Yii 核心程式碼都支援別名。例如 [[yii\caching\FileCache::cachePath]] 就同時支援路徑別名或普通的目錄地址。
 
路徑別名也和類的名稱空間密切相關。建議給每一個根名稱空間定義一個路徑別名,從而無須額外組態,便可啟動 Yii 的類自動載入機制。例如,因為有 `@yii` 指向 Yii 安裝目錄,那類似 `yii\web\Request` 的類就能被 Yii 自動載入。同理,若你用了一個第三方的類庫,如 Zend Framework,你只需定義一個名為 `@Zend` 的路徑別名指向該框架的安裝目錄。之後 Yii 就可以自動載入任意 Zend Framework 中的類了。
 
更多路徑別名資訊請參閱[路徑別名](concept-aliases.md)章節。
 

檢視(View)

Yii 2 中檢視最明顯的改動是檢視內的特殊變數 `$this` 不再指向當前控制器或小部件,而是指向**檢視**物件,它是 2.0 中引入的全新概念。**檢視**物件為 [[yii\web\View]] 的範例,他代表了 MVC 模式中的檢視部分。如果你想要在檢視中存取一個控制器或小部件,可以使用 `$this->context`。
 
要在其他檢視裡渲染一個區域性檢視,使用 `$this->render()`,而不是 `$this->renderPartial()`。`render()` 現在只返回渲染結果,而不是直接顯示它,所以現在你必須顯式地把它 **echo** 出來。像這樣:
 
echo $this->render('_item', ['item' => $item]);
 
除了使用 PHP 作為主要的模板語言,Yii 2.0 也裝備了兩種流行模板引擎的官方支援:Smarty 和 Twig。過去的 Prado 模板引擎不再被支援。要使用這些模板引擎,你需要組態 `view` 應用元件,給它設定 [[yii\base\View::$renderers|View::$renderers]] 屬性。具體請參閱[模板引擎](tutorial-template-engines.md)章節。
 
 

模型(Model)

Yii 2.0 使用 [[yii\base\Model]] 作為模型基礎類別,類似於 1.1 的 `CModel` 。`CFormModel` 被完全棄用了,現在要建立表單模型類,可以通過繼承 [[yii\base\Model]] 類來實現。
 
Yii 2.0 引進了名為 [[yii\base\Model::scenarios()|scenarios()]] 的新方法來宣告支援的場景,並指明在哪個場景下某屬性必須經過驗證,可否被視為安全值等等。如:
 
public function scenarios()
{
    return [
        'backend' => ['email', 'role'],
        'frontend' => ['email', '!role'],
    ];
}
 
上面的程式碼宣告了兩個場景:`backend` 和 `frontend` 。對於 `backend` 場景,`email` 和 `role` 屬性值都是安全的,且能進行批次賦值。對於 `frontend` 場景,`email` 能批次賦值而 `role` 不能。 `email` 和 `role` 都必須通過規則驗證。
 
[[yii\base\Model::rules()|rules()]] 方法仍用於宣告驗證規則。注意,由於引入了 [[yii\base\Model::scenarios()|scenarios()]],現在已經沒有 `unsafe` 驗證器了。
 
大多數情況下,如果 [[yii\base\Model::rules()|rules()]] 方法內已經完整地指定場景了,那就不必覆寫 [[yii\base\Model::scenarios()|scenarios()]],也不必宣告 `unsafe` 屬性值。
 
要了解更多有關模型的細節,請參考[模型](structure-models.md)章節。
 

控制器(Controller)

Yii 2.0 使用 [[yii\web\Controller]] 作為控制器的基礎類別,類似於 1.1 的 `CWebController`。使用 [[yii\base\Action]] 作為操作類的基礎類別。
 
這些變化最明顯的影響是,當你在寫控制器操作的程式碼時,應該返回(return)要渲染的內容而不是輸出(echo)它:
 
public function actionView($id)
{
    $model = \app\models\Post::findOne($id);
    if ($model) {
        return $this->render('view', ['model' => $model]);
    } else {
        throw new \yii\web\NotFoundHttpException;
    }
}
 
請檢視 [控制器(Controller)](structure-controllers.md) 章節了解有關控制器的更多細節。
 

小部件(Widget)

Yii 2.0 使用 [[yii\base\Widget]] 作為小部件基礎類別,類似於 1.1 的 `CWidget`。
 
為了讓框架獲得更好的 IDE 支援,Yii 2.0 引進了一個呼叫小部件的新語法。包含 [[yii\base\Widget::begin()|begin()]],[[yii\base\Widget::end()|end()]] 和 [[yii\base\Widget::widget()|widget()]] 三個靜態方法,用法如下:
 
use yii\widgets\Menu;
use yii\widgets\ActiveForm;

// 注意必須 **"echo"** 結果以顯示內容
echo Menu::widget(['items' => $items]);

// 傳遞一個用於初始化物件屬性的陣列
$form = ActiveForm::begin([
    'options' => ['class' => 'form-horizontal'],
    'fieldConfig' => ['inputOptions' => ['class' => 'input-xlarge']],
]);
... 表單輸入欄都在這裡 ...
ActiveForm::end();
更多細節請參閱[小部件](structure-widgets.md)章節。
 

主題(Theme)

2.0 主題的運作方式跟以往完全不同了。它們現在基於**路徑對映機制**,該機制會把一個源檢視檔案的路徑對映到一個主題檢視檔案路徑。舉例來說,如果路徑對映為 `['/web/views' => '/web/themes/basic']`,那麼 `/web/views/site/index.php` 檢視經過主題修飾的版本就會是 `/web/themes/basic/site/index.php`。也因此讓主題現在可以應用在任何檢視檔案之上,甚至是渲染控制器上下文環境之外的檢視檔案或小部件。
 
同樣,`CThemeManager` 元件已經被移除了。取而代之的 `theme` 成為了 `view` 應用元件的一個可組態屬性。
 
更多細節請參考[主題](output-theming.md)章節。
 

控制台應用(Console Application)

控制台應用現在如普通的 Web 應用程式一樣,由控制器組成,控制台的控制器繼承自 [[yii\console\Controller]],類似於 1.1 的 `CConsoleCommand`。
 
執行控制台命令使用 `yii <route>`,其中 `<route>` 代表控制器的路由(如 `sitemap/index`)。額外的匿名引數傳遞到對應的控制器操作方法,而有名的引數根據 [[yii\console\Controller::options()]] 的宣告來解析。
 
Yii 2.0 支援基於程式碼註釋自動生成相的關命令列幫助(help)資訊。
 
更多細節請參閱[控制台命令](tutorial-console.md)章節。

國際化(I18N)

Yii 2.0 移除了原來內建的日期格式器和數位格式器,為了支援 [PECL intl PHP module](http://pecl.php.net/package/intl)(PHP 的國際化擴充套件)的使用。
 
訊息翻譯現在由 `i18n` 應用元件執行。該元件管理一系列訊息源,允許使用基於訊息類別的不同訊息源。
 
更多細節請參閱[國際化(Internationalization)](tutorial-i18n.md)章節。
 

操作過濾器(Action Filters)

操作的過濾現在通過行為(behavior)來實現。要定義一個新的,自定義的過濾器,請繼承 [[yii\base\ActionFilter]] 類。要使用一個過濾器,需要把過濾器類作為一個 `behavior` 係結到控制器上。例如,要使用 [[yii\filters\AccessControl]] 過濾器,你需要在控制器內新增如下程式碼:
 
public function behaviors()
{
    return [
        'access' => [
            'class' => 'yii\filters\AccessControl',
            'rules' => [
                ['allow' => true, 'actions' => ['admin'], 'roles' => ['@']],
            ],
        ],
    ];
}
更多細節請參考[過濾器](structure-filters.md)章節。

前端資源(Assets)

Yii 2.0 引入了一個新的概念,稱為**資源包**(Asset Bundle),以代替 1.1 的指令碼包概念。
 
一個資源包是一個目錄下的資原始檔集合(如 JavaScript 檔案、CSS 檔案、圖片檔案等)。每一個資源包被表示為一個類,該類繼承自 [[yii\web\AssetBundle]]。用 [[yii\web\AssetBundle::register()]] 方法註冊一個資源包後,就使它的資源可被 Web 存取了,註冊了資源包的頁面會自動包含和參照資源包內指定的 JS 和 CSS 檔案。
 
更多細節請參閱 [前端資源管理(Asset)](structure-assets.md) 章節。
 

助手類(Helpers)

Yii 2.0 很多常用的靜態助手類,包括:
* [[yii\helpers\Html]]
* [[yii\helpers\ArrayHelper]]
* [[yii\helpers\StringHelper]]
* [[yii\helpers\FileHelper]]
* [[yii\helpers\Json]]
 
請參考[助手一覽](helper-overview.md) 章節來了解更多。
 

表單

Yii 2.0 引進了**表單欄(field)**的概念,用來建立一個基於 [[yii\widgets\ActiveForm]]的表單。一個表單欄是一個由標籤、輸入框、錯誤訊息(可能還有提示文字)組成的容器,被表示為 [[yii\widgets\ActiveField|ActiveField]] 物件。使用表單欄建立表單的過程比以前更整潔利落:
 

 <?php field($model, 'username') ?>
 <?php  field($model, 'password')->passwordInput() ?>
    
 
請參考[建立表單](input-forms.md)章節來了解更多細節。
 

查詢生成器(Query Builder)

 
Yii 1.1 中,查詢語句的生成分散在多個類中,包括 `CDbCommand`,`CDbCriteria` 以及 `CDbCommandBuilder`。Yii 2.0 以 [[yii\db\Query|Query]] 物件的形式表示一個資料庫查詢,這個物件使用 [[yii\db\QueryBuilder|QueryBuilder]] 在幕後生成 SQL 語句。例如:
 
$query = new \yii\db\Query();
$query->select('id, name')
      ->from('user')
      ->limit(10);

$command = $query->createCommand();
$sql = $command->sql;
$rows = $command->queryAll();
最重要的是,這些查詢生成方法還可以和[活動記錄](db-active-record.md)配合使用。
請參考[查詢生成器(Query Builder)](db-query-builder.md)章節了解更多內容。

活動記錄(Active Record)

Yii 2.0 的[活動記錄](db-active-record.md)改動了很多。兩個最顯而易見的改動分別涉及查詢語句的生成(query building)和關聯查詢的處理(relational query handling)。
1.1 中的 `CDbCriteria` 類在 Yii 2 中被 [[yii\db\ActiveQuery]] 所替代。這個類是繼承自 [[yii\db\Query]],因此也繼承了所有查詢生成方法。開始拼裝一個查詢可以呼叫 [[yii\db\ActiveRecord::find()]] 方法進行:
 
// 檢索所有 *活動的* 客戶和訂單,並以 ID 排序:
$customers = Customer::find()
    ->where(['status' => $active])
    ->orderBy('id')
    ->all();
要宣告一個關聯關係,只需簡單地定義一個 getter 方法來返回一個 [[yii\db\ActiveQuery|ActiveQuery]] 物件。getter 方法定義的屬性名(譯者注:即 getOrders() 中的 orders)表示關聯關係名。如,以下程式碼宣告了一個名為 `orders` 的關係(1.1 中必須在 `relations()` 方法內宣告關係):
 
class Customer extends \yii\db\ActiveRecord
{
    public function getOrders()
    {
        return $this->hasMany('Order', ['customer_id' => 'id']);
    }
}
現在你就可以通過呼叫 `$customer->orders` 來存取關聯表中某使用者的訂單了。你還可以用以下程式碼進行一場指定條件的實時關聯查詢:
 
$orders = $customer->getOrders()->andWhere('status=1')->all();
 
當貪婪載入一段關聯關係時,Yii 2.0 和 1.1 的運作機理並不相同。具體來說,在 1.1 中使用一條 JOIN 語句同時查詢主表和關聯表記錄。在 Yii 2.0 中會使用兩個沒有 JOIN 的 SQL 語句:第一條語句取回主表記錄,第二條通過主表記錄經主鍵篩選後查詢關聯表記錄。
 
當生成返回大量記錄的查詢時,可以鏈式書寫 [[yii\db\ActiveQuery::asArray()|asArray()]] 方法,這樣會以陣列的形式返回查詢結果,而不必返回
[[yii\db\ActiveRecord|ActiveRecord]] 物件,這能顯著降低因大量記錄讀取所消耗的 CPU 時間和記憶體。如:
 
$customers = Customer::find()->asArray()->all();
另一個改變是你不能再通過公共資料定屬性(Attribute)的預設值了。如果你需要這麼做的話,可以在你的記錄類的 `init` 方法中設定它們。
 
public function init()
{
    parent::init();
    $this->status = self::STATUS_NEW;
}
曾幾何時,在 1.1 中重寫一個活動記錄類的構造方法(Constructor)會導致一些問題。它們不會在 2.0 中出現了。需要注意的是,如果你需要在構造方法中新增一些引數,恐怕必須重寫 [[yii\db\ActiveRecord::instantiate()]] 方法。
活動記錄方面還有很多其他的變化與改進,請參考[活動記錄](db-active-record.md)章節以了解更多細節。
 

使用者及身份驗證介面(IdentityInterface)

1.1 中的 `CWebUser` 類現在被 [[yii\web\User]] 所取代,隨之 `CUserIdentity` 類也不在了。與之相對的,為達到相同目的,你可以實現 [[yii\web\IdentityInterface]] 介面,它使用起來更直觀。在高階應用模版裡提供了一個這樣的一個例子。
要了解更多細節請參考[認證(Authentication)](security-authentication.md),[授權(Authorization)](security-authorization.md)以及[高階應用模版](tutorial-advanced-app.md) 這三個章節。
 

URL 管理

Yii 2.0 的 URL 管理跟 1.1 中很像。一個主要的改進是現在的 URL 管理支援**可選引數**了。比如,如果你在 2.0 中定義了一個下面這樣的規則,那麼它可以同時匹配 `post/popular` 和 `post/1/popular` 兩種 URL。而在 1.1 中為達成相同效果,必須要使用兩條規則。
 
[
    'pattern' => 'post//',
    'route' => 'post/index',
    'defaults' => ['page' => 1],
]
請參考[URL 解析和生成](runtime-url-handling.md) 章節,以了解更多細節。.
 

同時使用 Yii 1.1 和 2.x

如果你有一些遺留的 Yii 1.1 程式碼,需要跟 Yii 2.0 一起使用,可以參考 [1.1 和 2.0 共用](extend-using-v1-v2.md)章節。