从 Yii 1.1 升级到 Yii 2.0:核心差异、迁移清单与源码级对照指南
从 Yii 1.1 升级到 Yii 2.0:核心差异、迁移清单与源码级对照指南
Yii 2.0 对框架进行了彻底重写,从 Yii 1.1 升级并非简单的版本号切换,而是一次涉及命名空间、对象模型、视图渲染、数据库层与扩展生态的全方位重构。本文以官方《Definitive Guide》的 Upgrading from Version 1.1 为骨架,逐项梳理两个版本间的重大差异,并结合当前仓库(gh_mirrors/yi/yii2)的框架源码(framework/ 目录)给出实现层面的印证,帮助你建立一份可执行的迁移清单。
如果你从未使用过 Yii 1.1,可以直接跳过本节,从 开始使用 Yii 2.0 读起。另外请注意,本文只覆盖了 2.0 引入的新特性中与升级最相关的一部分,完整能力请通读整个 Definitive Guide 目录——很多 1.1 时代需要自己手写的能力,如今已内置在核心代码中。
安装方式:全面拥抱 Composer
Yii 1.1 时代通常以压缩包或 SVN 方式部署框架;Yii 2.0 则完全基于 Composer(PHP 生态的事实标准包管理器)进行安装。核心框架本体与所有扩展都通过 Composer 管理,安装步骤请参考 安装 Yii。
对于扩展开发者,迁移意味着:
- 1.1 扩展若要在 2.0 中使用,需要按 2.0 的规范重新发布;
- 创建新扩展或改造旧扩展的具体流程,参考指南中的 Creating Extensions 章节;
- 扩展的自动加载依赖 Composer 的
autoload配置,或通过路径别名与 Yii 自带类加载器协作(见下文"路径别名")。
当前仓库根目录的 composer.json 中 autoload 部分即体现了 Yii 2.0 自身的类加载约定:框架核心类以 yii\ 为命名空间前缀映射到 framework/ 目录,这正是"命名空间即目录结构"约定的落地。
PHP 版本要求:从 5.2 到 5.4+
Yii 2.0 要求 PHP 5.4 或更高版本,相比 1.1 所需的 PHP 5.2 是一次巨大的进步。这一版本提升带来了语言层面的多项能力,迁移代码时需要特别注意:
- 命名空间(Namespaces):类与函数按命名空间组织,彻底取代 1.1 的全局类名约定;
- 匿名函数(Closures):事件处理器、回调、验证器配置中大量使用
function ($event) { ... }语法; - 短数组语法:使用
[...]取代array(...),这在配置数组、查询条件、规则声明中会频繁出现; - 短输出标签
<?=:视图文件中可直接使用,从 PHP 5.4 起可安全使用; - SPL 类与接口:如
ArrayObject、Countable等; - 后期静态绑定(Late Static Bindings):框架内部
static::className()、get_called_class()的实现基础; - DateTime 相关扩展;
- Traits:Yii 2.0 核心大量使用 trait 复用代码(例如
framework/db/ActiveQueryTrait.php就是ActiveQuery关系查询能力的 trait 实现); - intl 扩展:Yii 2.0 的国际化(I18N)功能依赖 PECL
intl模块,用于日期、数字格式化等。
命名空间:告别 C 前缀
Yii 2.0 最直观的变化是全面使用命名空间。几乎所有核心类都带上了命名空间前缀,例如 yii\web\Request、yii\base\Model。"C" 前缀(如 CWebUser、CController)已彻底取消,命名规范改为与目录结构一一对应:
yii\web\Request对应框架目录下的web/Request.php;yii\base\Model对应base/Model.php;yii\db\ActiveRecord对应db/ActiveRecord.php。
这一约定与 Yii 类加载器配合,让你无需显式 include 任何核心类文件即可直接使用(参见 类自动加载)。
Component 与 Object:一个类拆成两个
Yii 1.1 的 CComponent 在 2.0 中被拆分为两个基类:
| 基类 | 能力 | 适用场景 |
|---|---|---|
yii\base\BaseObject |
通过 getter/setter 定义对象属性 | 不需要事件/行为的类,如纯数据结构类 |
yii\base\Component |
继承 BaseObject,额外支持事件与行为 |
需要事件或行为能力的类 |
判断标准很简单:如果类不需要事件或行为特性,就用 BaseObject 作为基类。在源码层面,framework/base/Component.php 第 105 行明确写着 class Component extends BaseObject,它正是在父类属性特性之上叠加了 $_events、$_eventWildcards、$_behaviors 等成员;而 framework/base/BaseObject.php 只负责属性访问与对象初始化生命周期。
对象配置:统一构造器约定与 init() 生命周期
BaseObject 引入了一套统一的对象配置方式。任何 BaseObject 子类若需声明构造器,必须遵守以下约定——构造器最后一个参数必须是配置数组 $config,并在构造器末尾调用 parent::__construct($config):
class MyClass extends \yii\base\BaseObject
{
public function __construct($param1, $param2, $config = [])
{
// ... 配置应用之前的初始化
parent::__construct($config);
}
public function init()
{
parent::init();
// ... 配置应用之后的初始化
}
}
这套约定的背后是 BaseObject 构造器的标准流程(framework/base/BaseObject.php):
- 调用类构造器;
- 若
$config非空,则通过Yii::configure($this, $config)将配置中的名值对批量写入对象属性; - 调用
init()方法。
因此,需要"配置已就绪"的初始化逻辑应写在 init() 中,而不是构造器里。遵循此约定后,你可以用配置数组统一创建与配置对象:
$object = Yii::createObject([
'class' => 'MyClass',
'property1' => 'abc',
'property2' => 'cde',
], [$param1, $param2]);
Yii::createObject()(framework/BaseYii.php)在底层委托给 DI 容器 Yii::$container->get(),这也是为什么 2.0 的所有组件、widget、行为都能通过配置数组实例化。更多细节见 配置(Configurations)。
事件:任意事件名 + trigger()/on()/off()
Yii 1.1 中事件通过定义 on 前缀方法(如 onBeforeSave)实现;2.0 中你可以使用任意事件名。触发事件使用 trigger():
$event = new \yii\base\Event;
$component->trigger($eventName, $event);
绑定事件处理器使用 on(),解绑使用 off():
$component->on($eventName, $handler);
// 解绑:
// $component->off($eventName, $handler);
事件能力还大幅增强(见 Events):
- 处理器支持匿名函数、对象方法、静态类方法、全局函数四种形式(framework/base/Component.php 的文档注释有完整说明);
- 可通过
on()的第三个参数为处理器附加额外数据,在回调中经$event->data读取; - 支持在组件配置数组中用
'on add' => ...语法直接声明事件; - 从 2.0.14 起支持通配符事件(
$_eventWildcards); - 行为(Behavior)可通过重写
events()方法声明需要响应的事件(见下文 Active Record 行为一节)。
路径别名:@ 前缀 + 别名即自动加载
Yii 2.0 将路径别名的适用范围扩展为"文件/目录路径 + URL",并且强制要求别名以 @ 开头,以便与普通路径/URL 区分。例如 @yii 指向 Yii 安装目录。
路径别名在核心代码中被广泛支持,例如 yii\caching\FileCache 的 cachePath 属性既可以传普通目录路径,也可以传路径别名。
别名与类命名空间密切相关:建议为每个根命名空间定义一个路径别名,这样无需任何额外配置即可让 Yii 类自动加载器工作。例如:
@yii指向 Yii 安装目录 →yii\web\Request可被自动加载;- 使用第三方库(如 Zend Framework)时,定义
@Zend指向其安装目录 → Zend 的类也能被自动加载。
视图(Views):$this 变成 View 对象
2.0 中视图最显著的变化是:视图内的 $this 不再指向当前控制器或 widget,而是指向一个"视图对象"——yii\web\View 类型的实例,代表 MVC 中的视图部分。若要在视图中访问控制器或 widget,请使用 $this->context。
渲染子视图的方式也变了:
// 1.1: $this->renderPartial('_item', ...);
// 2.0: 使用 $this->render(),且必须显式 echo(render() 返回渲染结果而非直接输出)
echo $this->render('_item', ['item' => $item]);
模板引擎方面:
- 除 PHP 作为主模板语言外,2.0 官方支持 Smarty 与 Twig 两套模板引擎;
- Prado 模板引擎已不再支持;
- 使用这些引擎需通过
view应用组件的View::$renderers属性进行配置。
详见 模板引擎。
模型(Models):scenarios() 取代 unsafe
2.0 使用 yii\base\Model 作为模型基类(对应 1.1 的 CModel)。CFormModel 已被彻底移除,表单模型直接继承 yii\base\Model。
新引入的 scenarios() 方法用于声明支持的场景,并指明每个属性在场景下是否参与校验、是否安全(可批量赋值):
public function scenarios()
{
return [
'backend' => ['email', 'role'],
'frontend' => ['email', '!role'],
];
}
上例声明了两个场景:
backend:email与role均安全,可被批量赋值;frontend:email可批量赋值,role不可(!前缀表示不安全);- 两个属性都应通过
rules()中的规则参与校验。
rules() 方法仍然用来声明校验规则。由于 scenarios() 的引入,不再存在 unsafe 校验器。大多数情况下,如果 rules() 已经完整覆盖了所有场景、且无需声明 unsafe 属性,就不必重写 scenarios()——因为 Model::scenarios() 的默认实现(framework/base/Model.php)会自动从 rules() 声明中提取场景:默认场景 SCENARIO_DEFAULT 包含 rules() 中出现的所有属性,校验器上的 on/except 声明也会生成对应场景。
控制器(Controllers):action 返回内容而非 echo
2.0 以 yii\web\Controller 作为控制器基类(对应 CController),yii\base\Action 是动作类基类。对你代码影响最直接的一点是:控制器动作应返回要渲染的内容,而不是直接 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;
}
}
注意 findOne() 的返回值需要判空,未找到时抛出 NotFoundHttpException。详见 Controllers。
Widgets:begin()/end()/widget() 静态方法
2.0 以 yii\base\Widget 作为 widget 基类(对应 CWidget)。为获得更好的 IDE 支持,引入了新的静态方法语法 begin()、end()、widget()(framework/base/Widget.php 中这三个静态方法通过 Yii::createObject() 创建实例,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();
详见 Widgets。
主题(Themes):基于路径映射,不再有 CThemeManager
2.0 的主题机制完全重写,基于路径映射:把源视图文件路径映射到主题视图文件路径。例如主题路径映射为 ['/web/views' => '/web/themes/basic'],则视图文件 /web/views/site/index.php 的主题版本为 /web/themes/basic/site/index.php。因此,主题现在可以作用于任意视图文件,即使是控制器或 widget 上下文之外渲染的视图。
同时,CThemeManager 组件不复存在,theme 变成 view 应用组件的可配置属性。详见 主题化。
控制台应用:控制器化 + 自动帮助信息
控制台应用现在像 Web 应用一样以控制器组织。控制台控制器继承自 yii\console\Controller(对应 1.1 的 CConsoleCommand)。
运行控制台命令的语法为:
yii <route>
其中 <route> 是控制器路由(如 sitemap/index)。额外的匿名参数作为对应 action 方法的参数传入,具名参数则按照 Controller::options() 中的声明解析。
此外,Yii 2.0 支持从注释块自动生成命令帮助信息。详见 控制台命令。仓库根目录的 framework/yii 就是控制台入口脚本。
国际化(I18N):移除内置格式化器,改用 intl
2.0 移除了内置的日期格式化器与数字格式化器,转而使用 PECL intl 模块。
消息翻译改为通过 i18n 应用组件完成。该组件管理一组消息源(message sources),允许你根据消息分类使用不同的消息源(如数据库消息源、文件消息源等)。详见 国际化。
动作过滤器(Action Filters):行为化实现
2.0 中动作过滤器通过**行为(behavior)**实现:
- 自定义过滤器:继承
yii\base\ActionFilter; - 使用过滤器:将过滤器类作为行为附加到控制器。
例如在控制器中使用 yii\filters\AccessControl:
public function behaviors()
{
return [
'access' => [
'class' => 'yii\filters\AccessControl',
'rules' => [
['allow' => true, 'actions' => ['admin'], 'roles' => ['@']],
],
],
];
}
上面规则的含义:允许已登录用户(@)访问 admin 动作。完整的过滤器体系见 过滤器。
资源(Assets):Asset Bundle 取代脚本包
2.0 引入**资源包(asset bundle)**概念,取代 1.1 的脚本包(script packages)概念:
- 资源包是某个目录下资源文件(JS、CSS、图片等)的集合;
- 每个资源包用一个继承
yii\web\AssetBundle的类表示(AssetBundle本身继承自BaseObject,见 framework/web/AssetBundle.php); - 通过
AssetBundle::register()注册后,资源即通过 Web 可访问; - 与 1.1 不同,注册资源包的页面会自动包含该包声明的 JS/CSS 引用。
详见 资源管理。
助手类(Helpers):大量静态工具类
2.0 提供了许多常用的静态助手类,包括:
yii\helpers\Html:HTML 生成与转义;yii\helpers\ArrayHelper:数组操作(getValue、map、index、merge等);yii\helpers\StringHelper:字符串工具;yii\helpers\FileHelper:文件系统操作;yii\helpers\Json:JSON 编解码。
这些类在 framework/helpers/ 目录中均有对应实现。详见 助手类概览。
表单(Forms):field 概念让表单更简洁
2.0 基于 yii\widgets\ActiveForm 引入了 field(字段) 概念。一个 field 是"标签 + 输入控件 + 错误消息 + 提示文本"的容器,由 yii\widgets\ActiveField 对象表示。使用 field 构建表单比 1.1 更干净:
<?php $form = yii\widgets\ActiveForm::begin(); ?>
<?= $form->field($model, 'username') ?>
<?= $form->field($model, 'password')->passwordInput() ?>
<div class="form-group">
<?= Html::submitButton('Login') ?>
</div>
<?php yii\widgets\ActiveForm::end(); ?>
field 会依据模型的校验规则自动输出对应的错误消息与提示。详见 创建表单。
查询构建器(Query Builder):统一 Query 对象
1.1 中查询构建分散在 CDbCommand、CDbCriteria、CDbCommandBuilder 等多个类中;2.0 用一个 yii\db\Query 对象表示数据库查询,底层由 yii\db\QueryBuilder 将其转换为 SQL 语句:
$query = new \yii\db\Query();
$query->select('id, name')
->from('user')
->limit(10);
$command = $query->createCommand();
$sql = $command->sql;
$rows = $command->queryAll();
最关键的是:这些查询构建方法同样适用于 Active Record(见下节)。详见 查询构建器。
Active Record:ActiveQuery 取代 CDbCriteria
2.0 对 Active Record 做了大量改动,最明显的是查询构建与关系查询两方面。
查询:find() + ActiveQuery
1.1 的 CDbCriteria 被 yii\db\ActiveQuery 取代。ActiveQuery 继承自 yii\db\Query,因此继承了全部查询构建方法。通过 ActiveRecord::find() 开始构建查询(framework/db/ActiveRecord.php 中 find() 会创建 ActiveQuery 实例并传入当前 AR 类名):
// 取出所有 status 为 $active 的客户并按 ID 排序
$customers = Customer::find()
->where(['status' => $active])
->orderBy('id')
->all();
关系:getter 方法即关系
声明关系只需定义一个返回 ActiveQuery 对象的 getter 方法,getter 定义的属性名即关系名。1.1 需要在集中的 relations() 中声明,2.0 改为:
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();
预加载:两条 SQL 取代 JOIN
预加载(eager loading)关系时,2.0 与 1.1 做法不同:
- 1.1:创建一条 JOIN 查询同时选取主记录与关系记录;
- 2.0:执行两条不带 JOIN 的 SQL——第一条取回主记录,第二条用主记录的主键过滤取回关系记录(通过
with()指定,见 framework/db/ActiveQueryTrait.php 中的with()方法)。
asArray():大批量数据的内存优化
构建查询时链式调用 asArray()(framework/db/ActiveQueryTrait.php),可使查询结果以数组而非 ActiveRecord 对象返回。当记录数量很大时,可显著降低 CPU 与内存消耗:
$customers = Customer::find()->asArray()->all();
属性默认值:init() 而非公共属性
另一个变化是不能再通过公共属性定义属性默认值。如果需要默认值,请在记录类的 init() 方法中设置:
public function init()
{
parent::init();
$this->status = self::STATUS_NEW;
}
构造器与 instantiate()
1.1 中重写 ActiveRecord 构造器存在一些已知问题,2.0 已解决。注意:当为构造器新增参数时,可能需要重写 ActiveRecord::instantiate()。
Active Record 还有大量其他变化与增强,详见 Active Record。
Active Record 行为:直接继承 Behavior
2.0 移除了基类 CActiveRecordBehavior。要创建 Active Record 行为,需直接继承 yii\base\Behavior;若行为需要响应属主(owner)的某些事件,重写 events() 方法:
namespace app\components;
use yii\db\ActiveRecord;
use yii\base\Behavior;
class MyBehavior extends Behavior
{
// ...
public function events()
{
return [
ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate',
];
}
public function beforeValidate($event)
{
// ...
}
}
events() 返回"事件名 => 行为内方法"的映射,行为附加到组件后,这些处理器即自动挂载(行为机制详见 framework/base/Component.php 与 行为)。
用户与身份:User + IdentityInterface
1.1 的 CWebUser 由 yii\web\User 取代,CUserIdentity 类不复存在,取而代之的是实现 yii\web\IdentityInterface(framework/web/IdentityInterface.php),使用起来直观得多——只需实现 findIdentity()、getId()、getAuthKey()、validateAuthKey()(以及可选 findIdentityByAccessToken())等方法。高级项目模板(advanced project template)中提供了完整的实现示例。
URL 管理:可选参数 + 驼峰转连字符
URL 管理机制与 1.1 类似,一个重要增强是支持可选参数。例如下面的规则可以同时匹配 post/popular 与 post/1/popular,而在 1.1 中你需要两条规则:
[
'pattern' => 'post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
]
详见 URL 管理。
另一个命名约定变化:控制器与动作的驼峰命名会转换为小写加连字符,例如 CamelCaseController 的控制器 ID 为 camel-case。参见 controller IDs 与 action IDs。
旧代码共存:Yii 1.1 与 2.x 一起使用
如果存在遗留的 Yii 1.1 代码需要与 Yii 2.0 共存使用,请参考 Using Yii 1.1 and 2.0 Together 章节,其中说明了如何在 2.0 应用中加载并运行 1.1 代码。
迁移要点速查表
| 主题 | Yii 1.1 | Yii 2.0 |
|---|---|---|
| PHP 版本 | 5.2 | 5.4+(推荐更高版本) |
| 安装 | 压缩包/手工部署 | Composer |
| 类命名 | C 前缀(如 CController) |
命名空间 + 目录映射(如 yii\web\Controller) |
| 基类 | CComponent |
yii\base\BaseObject(轻量) / yii\base\Component(事件+行为) |
| 对象配置 | — | 构造器 $config 参数 + init() 生命周期 + Yii::createObject() |
| 事件 | on 前缀方法 |
trigger() / on() / off(),任意事件名 |
| 别名 | 无强制格式 | 必须以 @ 开头 |
| 视图 | $this 是控制器/widget |
$this 是 View 对象,用 $this->context 访问控制器 |
| 表单模型 | CFormModel |
直接继承 yii\base\Model |
| 场景 | — | scenarios() 声明,!attr 表示 unsafe |
| 控制器动作 | echo 输出 | return 渲染结果 |
| Widget | CWidget |
begin()/end()/widget() 静态方法 |
| 主题 | CThemeManager |
view 组件上的路径映射 theme 属性 |
| 控制台 | CConsoleCommand |
yii\console\Controller + yii <route> |
| 过滤器 | 过滤器类 | yii\base\ActionFilter 行为化 |
| 资源 | 脚本包 | yii\web\AssetBundle 资源包 |
| 查询 | CDbCommand/CDbCriteria/CDbCommandBuilder |
yii\db\Query + QueryBuilder |
| 关系 | 集中的 relations() |
getter 方法返回 ActiveQuery |
| 预加载 | JOIN 单查询 | 两条 SQL(无 JOIN) |
| 属性默认值 | 公共属性 | init() 中设置 |
| 用户身份 | CWebUser + CUserIdentity |
yii\web\User + 实现 IdentityInterface |
| 路由命名 | 原样 | 驼峰转连字符(camel-case) |
进一步阅读
迁移完成后,建议按顺序通读以下指南以充分利用 2.0 的新能力: