天天看點

Yii2.0 對比 Yii1.1 的重大改進 Yii2.0 對比 Yii1.1 的重大改進

Yii2.0 對比 Yii1.1 的重大改進

這部分内容是專門為已經有Yii1.1基礎的讀者朋友寫的。将Yii2.0與Yii1.1的不同點着重寫出來,對比學起來會快得多。 而對于從未接觸過Yii的讀者朋友,這部分内容掃一掃就可以了,作為對過往曆史的一個了解就夠了。 如果有的内容你一時沒看明白,也不要緊,本書的正文部分會講清楚的。 另外,沒有Yii1.1的經驗,并不妨礙對Yii2.0的學習。

Yii官方有專門的文檔歸納總結1.1版本和2.0版本的不同。以下内容,主要來自于官方的文檔,我做了下精簡, 選擇比較重要的變化,并加入了一些個人的經驗。

PHP新特性

從對PHP新特性的使用上,兩者就存在很大不同。Yii2.0大量使用了PHP的新特性,這在Yii1.1中是沒有的。是以,Yii2.0對于PHP的版本要求更高,要求PHP5.4及以上。Yii2.0中使用到的PHP新特性,主要有:

  • 命名空間(Namespace)
  • 匿名函數
  • 數組短文法形式: [1,2,3] 取代 array(1,2,3) 。這在多元數組、嵌套數組中,代碼更清晰、簡短。
  • 在視圖檔案中使用PHP的 <?= 标簽,取代 echo 語句。
  • 标準PHP庫(SPL) 類和接口,具體可以檢視 SPL Class and Interface
  • 延遲靜态綁定, 具體可以檢視 Late Static Bindings
  • PHP标準日期時間
  • 特性(Traits)
  • 使用PHP intl 擴充實作國際化支援, 具體可以檢視 PECL init 。

了解Yii2.0使用了PHP的新特性,可以避免開發時由于環境不當,特别是開發生産環境切換時,産生莫名其妙的錯誤。 同時,也是讓讀者朋友借機學習PHP新知識的意思。

命名空間(Namespace)

Yii2.0與Yii1.1之間最顯著的不同是對于PHP命名空間的使用。Yii1.1中沒有命名空間一說, 為避免Yii核心類與使用者自定義類的命名沖突,所有的Yii核心類的命名,均冠以 C 字首,以示差別。

而Yii2.0中所有核心類都使用了命名空間,是以, C 字首也就人老珠黃,退出曆史舞台了。

命名空間與實際路徑相關聯,比如 yii\base\Object 對應Yii目錄下的 base/Object.php 檔案。

基礎類

Yii1.1中使用了一個基礎類 CComponent ,提供了屬性支援等基本功能,是以幾乎所有的Yii核心類都派生自該類。 到了Yii2.0,将一家獨大的 CComponent進行了拆分。拆分成了 yii\base\Object 和 yii\base\Component 。 拆分的考慮主要是 CComponent 尾大不掉,有影響性能之嫌。 于是,Yii2.0中,把 yii\base\Object 定位于隻需要屬性支援,無需事件、行為。 而 yii\base\Component 則在前者的基礎上,加入對于事件和行為的支援。 這樣,開發者可以根據需要,選擇繼承自哪基礎類。

這一功能上的明确劃分,帶來了效率上的提升。在僅表示基礎資料結構,而非反映客觀事物的情況下, yii\base\Object 比較适用。

值得一提的是, yii\base\Object 與 yii\base\Component 兩者并不是同一層級的,前者是後者他爹。

事件(Event)

在Yii1.1中,通過一個 on 字首的方法來建立事件,比如 CActiveRecord 中的 onBeforeSave() 。 在Yii2.0中,可以任意定義事件的名稱,并自己觸發它:

1
2
3
4
5
6
7
8      
$event = new \yii\base\Event;
// 使用 trigger() 觸發事件
$component->trigger($eventName, $event);

// 使用 on() 前事件handler與對象綁定
$component->on($eventName, $handler);
// 使用 off() 解除綁定
$component->off($eventName, $handler);
      

别名(Alias)

Yii2.0中改變了Yii1.1中别名的使用形式,并擴大了别名的範疇。 Yii1.1中,别名以 . 的形式使用:

而在Yii2.0中,别名以 @ 字首的方式使用:

另外,Yii2.0中,不僅有路徑别名,還有URL别名:

1
2
3
4
5      
// 路徑别名
Yii::setAlias('@foo', '/path/to/foo');

// URL别名
Yii::setAlias('@bar', 'http://www.example.com');
      

别名與命名空間是緊密相關的,Yii建議為所有根命名空間都定義一個别名,比如上面提到的 yii\base\Object , 事實上是定義了 @yii 的别名,表示Yii在系統中的安裝路徑。 這樣一來,Yii就能根據命名空間找到實際的類檔案所在路徑,并自動加載。這一點上,Yii2.0與Yii1.1并沒有本質差別。

而如果沒有為根命名空間定義别名,則需要進行額外的配置。将命名空間與實際路徑的映射關系,告知Yii。

關于别名的更詳細内容請看 别名(Alias) 。

視圖(View)

Yii1.1中,MVC(model-view-controller)中的視圖一直是依賴于Controller的,并非是真正意義上獨立的View。 Yii2.0引入了 yii\web\View 類,使得View完全獨立。這也是一個相當重要變化。

首先,Yii2.0中,View作為Application的一個元件,可以在全局中代碼中進行通路。 是以,視圖渲染代碼不必再局限于Controller中或Widget中。

其次,Yii1.1中視圖檔案中的 $this 指的是目前控制器,而在 Yii2.0中,指的是視圖本身。 要在視圖中通路控制器,可以使用 $this->context 。這個 $this->context 是指誰調用了 yii\base\View::renderFile() 來渲染這個視圖。 一般是某個控制器,也可以是其他實作了 yii\base\ViewContextInterface接口的對象。

同時,Yii1.1中的 CClientScript 也被淘汰了,相關的前端資源及其依賴關系的管理,交由Assert Bundle yii\web\AssertBundle 來專職處理。 一個Assert Bundle代表一系列的前端資源,這些前端資源以目錄形式進行管理,這樣顯得更有序。 更為重要的是,Yii1.1中需要你格外注意資源在HTML中的順序,比如CSS檔案的順序(後面的會覆寫前面的), JavaScript檔案的順序(前後順序出錯會導緻有的庫未加載)等。 而在Yii2.0中,使用一個Assert Bundle可以定義依賴于另外的一個或多個Assert Bundle的關系, 這樣在向HTML頁面注冊這些CSS或者JavaScript時,Yii2.0會自動把所依賴的檔案先注冊上。

在視圖模版引擎方面,Yii2.0仍然使用PHP作為主要的模版語言。 同時官方提供了兩個擴充以支援目前兩大主流PHP模版引擎:Smarty和Twig,而對于Pardo引擎官方不再提供支援了。 當然,開發者可以通過設定 yii\web\View::$renderers 來使用其他模版。

另外,Yii1.1中,調用 $this->render('viewFile', ...) 是不需要使用 echo 指令的。 而Yii2.0中,記得 render() 隻是傳回視圖渲染結果,并不對直接顯示出來,需要自己調用 echo

如果有一天你發現怎麼Yii輸出了個空白頁給你,就要注意是不是忘記使用 echo 了。 還别說,這個錯誤很常見,特别是在對Ajax請求作出響應時,會更難發現這一錯誤。請你們程式設計時留意。

在視圖的主題(Theme)化方面,Yii2.0的運作機理采用了完全不同的方式。 在Yii2.0中,使用路徑映射的方式,将一個源視圖檔案路徑,映射成一個主題化後的視圖檔案路徑。 是以, ['/web/views' => '/web/themes/basic'] 定義了一個主題映射關系, 源視圖檔案 /web/views/site/index.php 主題化後将是 /web/themes/basic/site/index.php 。 是以, Yii1.1中的 CThemeManager 也被淘汰了。

模型(Model)

MVC中的M指的就是模型,Yii1.1中使用 CModel 來表示,而Yii2.0使用 yii\base\Model 來表示。

Yii1.1中, CFormModel 用來表示使用者的表單輸入,以差別于資料庫中的表。 這在Yii2.0中也被淘汰,Yii2.0傾向于使用繼承自 yii\base\Model 來表示送出的表單資料。

另外,Yii2.0為Model引入了 yii\base\Model::load() 和 yii\base\Model::loadMutiple() 兩個新的方法, 用于簡化将使用者輸入的表單資料指派給Model:

1
 2
 3
 4
 5
 6
 7
 8
 9
10      
// Yii2.0使用load()等同于下面Yii1.1的用法
$model = new Post;
if ($model->load($_POST)) {
    ... ...
}

// Yii1.1中常用的套路
if (isset($_POST['Post'])) {
    $model->attributes = $_POST['Post'];
}
      

另外一個重要變化就是Yii2.0中改變了Model應用于不同場景的邏輯。通過引入 yii\base\Model::scenarios() 來集中管理場景,使得一個Model所有适用的場景都比較清晰,一目了然。而Yii1.1是沒有一個統一管理場景的方法的。

由此帶來的一個很容易出現的問題就是,當你聲明一個Model處于某一場景時,可能由于拼寫錯誤, 不小心将場景的名稱寫錯了,那麼在Yii1.1中,這個錯誤的場景并沒有任何的提示。假設有以下情況:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23      
class UserForm extends CFormModel
{
    public $username;
    public $email;
    public $password;
    public $password_repeat;
    public $rememberMe=false;

    public function rules()
    {
        return array(
            // username 和 password 在所有場景中都要驗證
            array('username, password', 'required'),

            // email 和 password_repeat 隻在注冊場景中驗證
            array('email, password_repeat', 'required', 'on'=>'Registration'),
            array('email', 'email', 'on'=>'Registration'),

            // rememberMe 僅在登陸場景中驗證
            array('rememberMe', 'boolean', 'on'=>'Login'),
        );
    }
}
      

這裡針對UserForm的注冊和登陸兩個場景,設定了不同的驗證規則。接下來,你要在注冊場景中使用這個UserForm, 但你一不小心将 Registration 場景設定成了 SignUp , 說實在,我不是學英文出身的,這兩個單詞的意思在我眼裡是一樣一樣的。隻是Yii不會智能到把這兩個場景等同起來。 那麼Yii1.1将不會有任何的提示,并自動地使用第一個驗證規則,而使用者注冊時填寫的 email 和 password_repeat 字段就被抛棄了。這在實際程式設計中,是經常出現的一個低級錯誤。

從這裡可以看到,Yii1.1中對于場景,沒有一個集中統一的管理,也就是說一個Model可适用的場景, 是不确定的、任意的。通過 rules() 你很難一眼看出來一個Model可以适用于多少個場景,每個場景下都有哪些字段是有效的、需要驗證的。

而在Yii2.0中,由于引入了 yii\base\Model::scenarios() 新的方法, 将本Model所有适用的場景,及不同場景下的有效字段都進行了聲明, 這個邏輯就顯得清晰了。而且,如果使用了一個未聲明的場景,Yii2.0會有相應的提示, 這避免了上面這個低級錯誤的可能:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14      
namespace app\models;

use yii\db\ActiveRecord;

class User extends ActiveRecord
{
    public function scenarios()
    {
        return [
            'login' => ['username', 'password', 'rememberMe'],
            'registration' => ['username', 'email', 'password', 'password_repeat'],
        ];
    }
}
      

這樣看來,是不是很清晰?這個User僅有兩種場景,每種場景的有效字段也一目了然。 而至于具體場景下每個字段的驗證規則,仍然由 yii\base\Model::rules() 來确定。 這也意味着, unsafe 驗證在Yii2.0中也沒有了立足之地,凡是 unsafe 的字段,就不在特定的場景中列出來。 或者為了更加明顯的表示某一字段在特定場景下是無效的,可以給這個字段加上 ! 字首。

在預設情況下, yii\base\Model::scenarios() 所有适用的場景和對應的字段由 yii\base\Model::rules() 的内容自動生成。也就是說,如果你的 rules()很完備、很清晰,那麼也是不需要重載這個 scenarios() 的。 這種情況下,Yii1.1和Yii2.0在這一點上的表現形式,是一樣的。但是,個人經驗看, 我更傾向于将 scenarios() 聲明清楚,而在 rules() 中,僅指定字段的驗證規則,而不涉及場景的内容。 這樣的邏輯更加清晰,便于其他團隊成員閱讀你的代碼,也便于後續的維護和開發。

控制器(Controller)

除了上面講到的控制器中要使用 echo 來顯示渲染視圖的輸出這點差別外, Yii1.1與Yii2.0的控制器還表現出更為明顯的差別,那就是動作過濾器(Action Filter) 的不同。

在Yii2.0中,動作過濾器以行為(behavior)的方式出現, 一般繼承自 yii\base\ActionFilter ,并注入到一個控制器中,以發生作用。比如,Yii1.1中很常見的:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11      
public function behaviors()
{
    return [
        'access' => [
            'class' => 'yii\filters\AccessControl',
            'rules' => [
                ['allow' => true, 'actions' => ['admin'], 'roles' => ['@']],
            ],
        ],
    ];
}
      

看着是不是有點像,但又确實不一樣?

Active Record

還記得麼?在Yii1.1中,資料庫查詢被分散成 CDbCommand , CDbCriteria 和 CDbCommandBuilder 。 所謂天下大勢分久必合,到了Yii2.0,采用 yii\db\Query 來表示資料庫查詢:

1
2
3
4
5
6
7
8      
$query = new \yii\db\Query();
$query->select('id, name')
      ->from('user')
      ->limit(10);

$command = $query->createCommand();
$sql = $command->sql;
$rows = $command->queryAll();
      

最最最爽的是, yii\db\Query 可以在 Active Record中使用,而在Yii1.1中,要結合兩者,并不容易。

Active Record在Yii2.0中最大的變化一個是查詢的建構,另一個是關聯查詢的處理。

Yii1.1中的 CDbCriteria 在Yii2.0中被 yii\db\ActiveQuery 所取代, 這個把前輩拍死在沙灘上的家夥,繼承自 yii\db\Query ,是以可以進行類似上面代碼的查詢。 調用 yii\db\ActiveRecord::find() 就可以啟動查詢的建構了:

$customers = Customer::find()
    ->where(['status' => $active])
    ->orderBy('id')
    ->all();
      

這在Yii1.1中,是不容易實作的。特别是比較複雜的查詢關系。

在關聯查詢方面,Yii1.1是在一個統一的地方 relations() 定義關聯關系。 而Yii2.0改變了這一做法,定義一個關聯關系:

  • 定義一個getter方法
  • getter方法的方法名表示關聯關系的名稱,如 getOrders() 表示關系 orders
  • getter方法中定義關聯的依據,通常是外鍵關系
  • getter傳回一個 yii\db\ActiveQuery 對象

比如以下代碼就定義了 Customer 的 orders 關聯關系:

1
 2
 3
 4
 5
 6
 7
 8
 9
10      
class Customer extends \yii\db\ActiveRecord
{
    ... ...

    public function getOrders()
    {
        // 關聯的依據是 Order.customer_id = Customer.id
        return $this->hasMany('Order', ['customer_id' => 'id']);
    }
}
      

這樣的話,可以通過 Customer 通路關聯的 Order

1
2
3
4
5      
// 擷取所有與目前 $customer 關聯的 orders
$orders = $customer->orders;

// 擷取所有關聯 orders 中,status=1 的 orders
$orders = $customer->getOrders()->andWhere('status=1')->all();
      

對于關聯查詢,有積極的方式也有消極的方式。差別在于采用積極方式時,關聯的查詢會一并執行, 而消極方式時,僅在顯示調用關聯記錄時材會執行關聯的查詢。

在積極方式的實作上,Yii2.0與Yii1.1也存在不同。Yii1.1使用一個JOIN查詢, 來實作同時查詢主記錄及其關聯的記錄。 而Yii2.0棄用JOIN查詢的方式,而使用兩個順序的SQL語句, 第一個語句查詢主記錄,第二個語句根據第一個語句的傳回結果進行過濾。

同時,Yii2.0為Active Record引入了 asArray() 方法。在傳回大量記錄時,可以以數組形式儲存, 而不再以對象形式儲存,這樣可以節約大量的空間,提高效率。

另外一個變化是,在Yii1.1中,字段的預設值可以通過為類的public 成員變量賦初始值來指定。 而在Yii2.0中,這樣的方式是行不通的,必須通過重載 init() 成員函數的方式實作了。

Yii2.0還淘汰掉了原來的 CActiveRecordBehavior 類。在Yii2.0中,将行為與類進行綁定采用了統一的方式進行, 具體請參考 行為(Behavior) 的有關内容。

Yii2.0中,ActiveRecord得到極大的加強,在相關的章節中我們已經進行專門的講解。

這裡的内容主要是點一點Yii2.0之于Yii1.1的變化。大緻了解下就可以了, 主要還是要看正文專門針對每個知識點的深入講解。

繼續閱讀