Yii 2 升级实战指南:从 Yii 1.1 迁移到 2.x 的核心差异与代码改造方案
【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址: https://gitcode.com/gh_mirrors/yi/yii2
Yii 2 是一次对 Yii 1.1 的完全重写,两个版本之间的差异远大于普通的小版本升级。本文以官方意大利语升级指南(docs/guide-it/intro-upgrade-from-v1.md)为骨架,结合当前仓库(Yii 2.0.56-dev)的源码实现,系统梳理从 1.1 迁移到 2.x 时需要掌握的全部关键差异:从安装方式、PHP 语言特性、命名空间、对象模型,到视图、模型、控制器、Active Record、权限、URL 管理等每一个被重写的子系统。读完本文,你将能够评估既有 1.1 代码的迁移工作量,并按照 2.x 的规范完成每一类代码的逐项改造。
提示:本文引用的官方文档均以仓库根目录为起点给出相对路径;与英文指南存在翻译差异时,以仓库实际存在的 英文权威指南 为准。如果你从未使用过 Yii 1.1,可以跳过本文,直接阅读 安装指南。
写在前面:为什么这不是一次"小版本升级"
Yii 1.1 与 2.0 之间没有平滑的升级路径,因为框架的核心架构被完全重写。升级动作不能像从 1.1.x 升级到 1.1.y 那样原地替换,而是需要按照新框架的约定逐类改造你的业务代码。与此同时,Yii 2.0 引入了远超本文综述的新功能——很多过去需要自己动手实现的功能(例如自动加载、依赖注入容器、表单字段、行为与过滤器体系)如今已经在框架核心中内置,迁移过程中应当顺手移除旧的自制代码。
本节对应官方指南正文,详细差异见 英文版指南 的对应章节,各子系统均有独立专题文档,本文会在每节末尾给出延伸阅读链接。
安装方式:全面切换到 Composer
Yii 2.0 的安装与扩展管理完全基于 Composer——PHP 生态的事实标准包管理器。无论是最小化骨架(basic)还是高级项目模板(advanced),安装、更新、扩展依赖都由 Composer 统一驱动。
- 框架核心与扩展统一通过
composer.json声明依赖; - 创建新扩展、或将 1.1 扩展改造为 2.0 扩展时,需遵循 创建扩展 一节中约定的目录结构与包规范。
具体安装步骤(包括 Composer 安装、create-project用法、目录结构说明)请参阅 安装 Yii。
PHP 版本要求与语言层面的差异
Yii 2.0 将最低 PHP 版本从 1.1 时代的PHP 5.2大幅提升到PHP 5.4+。这一跃迁直接带来了如下语言特性红利,也是迁移时最容易出现语法错误的地方:
| 特性 | 说明 | 在 Yii 2 中的体现 |
|---|---|---|
| 命名空间(Namespace) | 类/函数/常量的逻辑分组 | 全部核心类均位于yii\*命名空间下 |
| 匿名函数(Closure) | 回调、事件处理器 | 事件on()、URL 规则回调等大量使用 |
| 短数组语法 | [...]取代array(...) | 配置数组、规则声明全面采用短语法 |
| 短 echo 标签 | <?=在视图中安全可用 | 视图模板中的标准输出写法 |
| SPL 接口与类 | 迭代器、异常体系 | 异常层次、集合类的基础 |
| 后期静态绑定(Late Static Binding) | static:: | Active Record 的find()等静态方法可被子类正确解析 |
| 日期时间类 | DateTime家族 | 格式化、时区处理的基础 |
| Trait | 横向复用 | Yii 2 多处内置 trait 复用代码 |
| intl 扩展 | 国际化能力 | 2.0 的日期/数字格式化与翻译均依赖它 |
需要提醒的是:PHP 5.4 是撰写官方升级指南时(Yii 2.0 早期)的最低门槛;当前仓库的框架版本为 2.0.56-dev(见 framework/BaseYii.php 中的getVersion()返回值),随着版本演进,对 PHP 运行时版本的要求也在逐步提高,实际部署请以你所安装版本的 framework/composer.json 中的require.php约束为准。
命名空间:类名"去 C 化"与目录结构对齐
Yii 2.0 最直观的变化是全面引入命名空间,几乎所有核心类都被放入命名空间,例如yii\web\Request。同时,1.1 时代的 "C" 前缀命名惯例(如CController、CComponent)被彻底废弃。
命名规则与目录结构严格对齐:yii\web\Request表示该类定义在框架根目录的web/Request.php文件中(即 framework/web/Request.php)。得益于 Yii 内置的类加载器(autoloader),你无需手工require任何核心类文件,直接引用类名即可完成加载。
组件与对象:CComponent 一分为二
1.1 中包罗万象的CComponent在 2.0 中被拆分为两个职责清晰的基类:
- [[yii\base\BaseObject]]:轻量级基类,通过 getter/setter 提供对象属性(property)机制。适合"纯数据结构"类,例如参数载体、配置包装。
- [[yii\base\Component]]:继承
BaseObject,在属性机制之上叠加了事件(event)与行为(behavior)能力。
从 framework/base/BaseObject.php 的源码可以看到,BaseObject通过__get()/__set()魔术方法实现了"属性即 getter/setter 调用"的约定:读取$object->label等价于调用$object->getLabel(),写入等价于调用setLabel();只有 getter 没有 setter 的属性是只读的,尝试写入会抛出InvalidCallException。
选型建议:如果类不需要事件或行为,优先继承BaseObject(或直接实现属性约定),保持轻量;需要事件、行为、生命周期钩子(如init())的组件则继承Component。
对象配置:统一的构造约定与 Yii::createObject
BaseObject引入了统一的"配置数组驱动对象初始化"约定,这是理解 Yii 2 一切配置(组件配置、DI 容器、行为声明)的钥匙。源码中 BaseObject::__construct() 的执行顺序是:
- 调用类的构造函数;
- 用传入的
$config配置数组初始化对象属性(内部调用Yii::configure()); - 调用
init()方法。
因此,子类若需要自定义构造函数,必须遵循如下约定——最后一个参数必须是$config = [],并且在构造函数的末尾调用parent::__construct($config):
class MyClass extends \yii\base\BaseObject { public function __construct($param1, $param2, $config = []) { // ... 配置应用前的初始化 parent::__construct($config); } public function init() { parent::init(); // ... 配置应用后的初始化 } }其中init()的设计意图是:属性配置已在构造阶段全部生效,因此"依赖配置结果的初始化"都应放进init()而不是构造函数。
遵循上述约定后,即可用配置数组创建并初始化任意对象:
$object = Yii::createObject([ 'class' => 'MyClass', 'proprieta1' => 'abc', 'proprieta2' => 'cde', ], [$param1, $param2]);从 BaseYii::createObject() 的实现可以看到,createObject()对字符串类型会委托给依赖注入容器Yii::$container->get(),对配置数组则通过容器创建并应用配置;而 Yii::configure() 的本质就是把配置数组的每个键值对赋值给对象的对应属性。这套约定是整个框架"配置驱动"的基础,深入内容见 对象配置。
事件:从 onXxx 命名法到任意事件名 + trigger/on/off
1.1 中事件只能通过onBeforeSave这类固定命名方法定义;2.0 打破了这一限制,事件名可以是任意字符串,并通过三个显式方法管理:
// 触发事件:创建事件对象,交给 trigger() $event = new \yii\base\Event; $component->trigger($eventName, $event); // 绑定事件处理器 $component->on($eventName, $handler); // 解绑事件处理器 $component->off($eventName, $handler);结合 framework/base/Component.php 的源码(_events属性存储事件名到处理器列表的映射)可以确认:多个处理器按绑定顺序依次执行;事件名区分大小写;处理器支持匿名函数、对象方法、静态类方法、全局函数四种形式。
2.0 还对事件体系做了大量增强——例如通配符事件(自 2.0.14 起支持*匹配,见_eventWildcards属性)、在配置数组中用'on add' => $handler直接绑定事件、用$event->data传递附加数据等。详见 事件专题。
路径别名:必须带 @ 前缀,与命名空间深度绑定
2.0 将路径别名(Path Alias)的应用范围从本地目录扩展到本地文件、远程 URL,并规定别名必须以@开头,以便与普通路径/URL 明确区分。例如@yii指向 Yii 框架安装目录,@webroot、@app等则在入口脚本中注册。
别名在框架大部分核心代码中被原生支持,例如[[yii\caching\FileCache::cachePath]]既可直接接收目录路径,也可接收别名。
从 BaseYii::getAlias() 的实现可以看出别名解析的两条关键规则:
- 最长匹配优先:注册了
@foo与@foo/bar后,解析@foo/bar/config会用@foo/bar(而不是@foo)替换,因为setAlias()内部对同一根别名下的子别名做了krsort排序; /是边界字符:解析@foo/barbar/config时只能命中@foo,不会误伤@foo/bar。
更重要的约定是别名与命名空间强关联:为每个根命名空间注册一个别名后,就能零配置享受 Yii 的自动加载。例如@yii对应框架安装目录,yii\web\Request就能被自动加载;使用 Zend 等第三方库时,只需注册@Zend指向其安装目录,Yii 即可自动加载该库的全部类。详见 别名专题。
视图:$this 从"当前控制器"变为 View 对象
2.0 视图系统最重要的变化:视图中的$this不再指向当前控制器或 widget,而是指向新引入的视图对象[[yii\web\View]](MVC 中 V 的载体)。想从视图访问控制器或 widget,通过$this->context获取。
渲染行为的两个关键调整:
- 在视图中渲染另一个视图(局部视图)使用
$this->render()而不是 1.1 的renderPartial(); render()现在返回渲染结果字符串,必须显式echo输出:
echo $this->render('_item', ['item' => $item]);模板引擎方面,PHP 仍是默认模板语言;2.0 官方支持 Smarty 与 Twig 两个备选引擎,通过配置view组件的 [[yii\base\View::$renderers|View::$renderers]] 属性启用;1.1 时代的 Prado 模板引擎不再受支持。详见 模板引擎。
模型:scenarios() 取代 unsafe 声明
2.0 以 [[yii\base\Model]] 作为所有模型基类(对应 1.1 的CModel),并移除了CFormModel——表单模型同样继承Model即可。
新增的 [[yii\base\Model::scenarios()|scenarios()]] 方法用于集中声明:支持的场景有哪些、每个场景下哪些属性参与验证、哪些属性是安全的(可批量赋值):
public function scenarios() { return [ 'backend' => ['email', 'role'], 'frontend' => ['email', '!role'], ]; }语义解读:backend场景下email、role都是安全的,可被批量赋值;frontend场景下email安全,role以!前缀标记为unsafe(不可批量赋值)。两个字段仍需在rules()中声明验证规则。
关于scenarios()与rules()的关系,源码 Model::scenarios() 的默认实现揭示了一个便利规则:默认情况下scenarios()会根据rules()中每个验证器的on/except自动推导场景集合,SCENARIO_DEFAULT场景包含rules()中的所有属性。因此:
- 如果
rules()已覆盖全部场景,且不需要 unsafe 属性,就无需重写scenarios(); - 只有需要精细控制属性安全性时才需要显式重写。
模型细节见 模型专题。
控制器:动作必须 return 而不是 echo
控制器基类从CController变为 [[yii\web\Controller]],动作类基类为 [[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; } }注意render()返回字符串、由框架统一输出,配合异常体系(如NotFoundHttpException)即可优雅处理 404。详见 控制器专题。
Widget:begin() / end() / widget() 三段式 API
Widget 基类从CWidget变为 [[yii\base\Widget]]。为获得更好的 IDE 支持与更清晰的代码结构,2.0 引入了begin()、end()、widget()三个静态方法:
use yii\widgets\Menu; use yii\widgets\ActiveForm; // 简单渲染型 widget:注意需要 echo 输出结果 echo Menu::widget(['items' => $items]); // 内容包裹型 widget:begin/end 成对出现,可传入配置数组初始化属性 $form = ActiveForm::begin([ 'options' => ['class' => 'form-horizontal'], 'fieldConfig' => ['inputOptions' => ['class' => 'input-xlarge']], ]); ... 表单输入字段 ... ActiveForm::end();从 Widget::begin() 的实现可以看到,begin()会把get_called_class()写进配置并调用Yii::createObject()创建实例,随后压入静态栈self::$stack,end()再从栈顶弹出并渲染——这就是"begin/end 必须正确嵌套"的底层原因;Widget::widget() 则用输出缓冲(ob_start())捕获渲染结果并返回字符串。详见 Widget 专题。
主题:基于路径映射,CThemeManager 退场
2.0 的主题机制与 1.1 完全不同,改为路径映射:把"源视图路径"映射到"主题视图路径"。例如映射['/web/views' => '/web/themes/basic']时,源视图/web/views/site/index.php的主题化版本就是/web/themes/basic/site/index.php。
由此带来的能力提升:主题现在可以作用于任意视图文件,包括不在控制器或 widget 上下文内渲染的视图。CThemeManager组件被移除,取而代之的是view组件上一个可配置的theme属性。详见 主题专题。
控制台应用:yii 与注解式帮助
控制台应用与 Web 应用结构统一——都是 controller 模型。命令控制器继承 [[yii\console\Controller]](对应 1.1 的CConsoleCommand)。
命令行执行方式为yii <route>,<route>即控制器路由(例如sitemap/index)。参数传递规则:
- 匿名参数按顺序传给动作方法的对应参数;
- 命名参数按 [[yii\console\Controller::options()]] 的声明处理。
2.0 还能从命令控制器方法的 PHPDoc 注释块中自动提取并生成命令行帮助信息。详见 控制台命令专题。
I18N:intl 取代内置格式化,i18n 组件管理翻译
2.0 移除了 1.1 内置的日期/数字格式化逻辑,全面改用 PHP 的 PECL intl 模块(这也是 PHP 5.4+ 特性清单中要求 intl 扩展的原因)。
消息翻译由i18n组件承担:该组件管理一组消息源(message source),从而可以按分类(category)使用不同的消息源(如数据库、PHP 文件、gettext PO 文件等)。详见 国际化专题。
动作过滤器:基于行为实现
2.0 的动作过滤器(action filter)本身就是行为(behavior)。自定义过滤器需继承 [[yii\base\ActionFilter]](其源码 framework/base/ActionFilter.php 继承自Behavior,并支持only/except限定过滤范围、自 2.0.9 起支持site/*通配符);使用过滤器则把过滤器类挂到控制器的behaviors()上。例如接入 [[yii\filters\AccessControl]]:
public function behaviors() { return [ 'access' => [ 'class' => 'yii\filters\AccessControl', 'rules' => [ ['allow' => true, 'actions' => ['admin'], 'roles' => ['@']], ], ], ]; }roles => ['@']表示仅已登录用户可访问admin动作。详见 过滤器专题。
Asset:AssetBundle 取代脚本包
2.0 引入asset bundle概念,取代 1.1 的脚本包(script packages)。asset bundle 是位于同一目录下的一组资源文件(JS、CSS、图片等)的集合,每个 bundle 由继承 [[yii\web\AssetBundle]] 的类表示;调用 [[yii\web\AssetBundle::register()]] 即可把该包资源发布为可通过 Web 访问的 URL。
与 1.1 的关键差异:注册 bundle 的页面会自动包含其中声明的 JS 与 CSS 引用,无需再手工输出<script>/<link>标签。详见 资源管理专题。
Helper:一批高价值静态工具类
2.0 内置了大量常用静态辅助类,迁移时优先用它们替换手写逻辑:
- [[yii\helpers\Html]]——HTML 生成与转义
- [[yii\helpers\ArrayHelper]]——数组操作
- [[yii\helpers\StringHelper]]——字符串处理
- [[yii\helpers\FileHelper]]——文件系统操作
- [[yii\helpers\Json]]——JSON 编解码
完整列表与用法见 Helper 概览。
表单:ActiveField 字段模型
2.0 的表单构建围绕**字段(field)**概念展开:一个字段 = 标签 + 输入控件 + 错误信息 + 提示文本,由 [[yii\widgets\ActiveField]] 对象表示。配合 [[yii\widgets\ActiveForm]] 可以让表单代码显著更简洁:
<?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(); ?>$form->field()自动完成标签、输入控件、错误消息的装配,.passwordInput()等链式方法则调整输入类型。详见 表单专题。
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, nome') ->from('user') ->limit(10); $command = $query->createCommand(); $sql = $command->sql; $rows = $command->queryAll();更大的红利:同一套查询构建方法可以无缝用于 Active Record。详见 Query Builder 专题。
Active Record:ActiveQuery、关系 getter 与两条 SQL
2.0 对 Active Record 的改动众多,最直观的是查询构建与关系管理两大块。
CDbCriteria 退场,ActiveQuery 登场
1.1 的CDbCriteria被 [[yii\db\ActiveQuery]] 取代,后者继承 [[yii\db\Query]],因此继承了全部查询构建方法;查询从调用 [[yii\db\ActiveRecord::find()]] 开始:
// 获取所有"启用"状态的客户并按 id 排序 $clienti = Clienti::find() ->where(['stato' => $attivo]) ->orderBy('id') ->all();关系 = getter 返回 ActiveQuery
声明关系只需定义一个返回 [[yii\db\ActiveQuery]] 的 getter,getter 属性名即关系名(1.1 时代需要在relations()方法中声明):
class Cliente extends \yii\db\ActiveRecord { public function getOrdini() { return $this->hasMany('Ordine', ['cliente_id' => 'id']); } }使用方式:$cliente->ordini直接访问关联记录;也可以链式追加查询条件实现"带条件的关联查询":
$ordini = $cliente->getOrdini()->andWhere('stato=1')->all();预加载(eager loading)从 JOIN 变为两条 SQL
预加载关系时,2.0 与 1.1 行为不同:1.1 生成一条包含 JOIN 的 SQL 同时取主记录与关联记录;2.0 改为执行两条独立 SQL——第一条加载主表行,第二条基于主表行取回的关键字加载关联表行。
大数据量:asArray()
对返回大量结果的查询,可用 [[yii\db\ActiveQuery::asArray()|asArray()]] 让结果以数组形式返回,避免实例化 ActiveRecord 对象,从而节省 CPU 与内存:
$clienti = Cliente::find()->asArray()->all();默认值移到 init()
2.0 不再支持通过公共属性声明属性默认值;需要默认值就在 ActiveRecord 的init()中设置:
public function init() { parent::init(); $this->stato = self::STATO_NUOVO; }构造函数可以安全覆写
1.1 中覆写 ActiveRecord 构造函数容易出问题,2.0 已修复。注意:若需要给构造函数增加参数,通常应覆写 [[yii\db\ActiveRecord::instantiate()]] 来完成。更多变化见 Active Record 专题。
Active Record 行为:直接继承 Behavior
1.1 的CActiveRecordBehavior基类被移除,2.0 中自定义行为直接继承yii\base\Behavior;若需要响应 owner 的事件,覆写events()方法返回事件映射即可:
namespace app\components; use yii\db\ActiveRecord; use yii\base\Behavior; class MioBehavior extends Behavior { // ... public function events() { return [ ActiveRecord::EVENT_BEFORE_VALIDATE => 'beforeValidate', ]; } public function beforeValidate($event) { // ... } }用户与身份:IdentityInterface 取代 CUserIdentity
1.1 的CWebUser由 [[yii\web\User]] 取代,CUserIdentity被移除。2.0 的身份认证要求你为 User 模型实现 [[yii\web\IdentityInterface]],接口比旧的身份类直观得多(只需实现findIdentity()、getId()、getAuthKey()、validateAuthKey()等少数方法)。高级应用模板(advanced template)中提供了现成的实现范例。详见 认证、授权 两节。
URL 管理:规则支持参数与默认值
2.0 的 URL 管理机制与 1.1 相似,但一个显著增强是规则支持参数与默认值。下面这条规则可以同时匹配post/popolari与post/1/popolari(在 1.1 中需要两条规则):
[ 'pattern' => 'post/<page:\d+>/<tag>', 'route' => 'post/index', 'defaults' => ['page' => 1], ]<page:\d+>是带正则约束的参数,defaults为缺省参数提供默认值。详见 URL 管理(注意:意大利语版指南中此处的链接写作runtime-url-handling.md,仓库中对应权威文档为docs/guide/runtime-routing.md)。
新旧共存:Yii 1.1 与 2.x 混用
如果希望保留一部分 1.1 旧代码并在 2.0 项目中继续使用,官方提供了"在同一应用中同时运行 Yii 1.1 与 2.0"的集成方案,具体做法(如各自初始化两套框架、共享会话与数据库连接等)请参阅 同时使用 Yii 1.1 与 2.0。
结语:一份可执行的迁移检查清单
综合全文,从 1.1 迁移到 2.x 时可按下述顺序推进,每一行都能在本文对应小节找到改造样例与源码依据:
- 基础设施:改用 Composer 管理依赖与安装(见"安装方式");确认 PHP 版本满足当前发行版要求并开启
intl(见"PHP 版本要求")。 - 类结构:类名去 "C" 前缀并放入命名空间,目录与命名空间对齐(见"命名空间");区分
BaseObject与Component的继承选择(见"组件与对象")。 - 对象创建:构造函数统一为"末尾
$config = []+ 末尾调parent::__construct($config)";用Yii::createObject()创建对象(见"对象配置")。 - 事件与过滤器:改用
trigger()/on()/off();自定义过滤器改继承ActionFilter并挂载到behaviors()(见"事件""动作过滤器")。 - 路径与资源:别名统一加
@前缀;脚本包改造成AssetBundle(见"路径别名""Asset")。 - 视图与控制器:
$this改为视图对象,render()结果需echo;控制器动作改为return渲染结果(见"视图""控制器")。 - 模型与表单:用
scenarios()替代 unsafe 声明;表单改用ActiveForm+ActiveField(见"模型""表单")。 - 数据层:
CDbCriteria换成ActiveQuery+find();关系改为 getter 返回ActiveQuery;默认值移入init();行为直接继承Behavior(见"Query Builder""Active Record""AR 行为")。 - 用户体系:实现
IdentityInterface接入yii\web\User(见"用户与身份")。 - 外围:主题改路径映射、控制台命令用
yii <route>、格式化依赖intl、URL 规则支持参数默认值(见对应章节)。
迁移完成后,建议通读英文权威指南的其余章节(从 安装 Yii 开始),因为 2.0 引入的很多新能力(DI 容器、行为系统、控制台、REST、测试等)在 1.1 中并不存在,值得在迁移过程中一并纳入架构设计。
【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址: https://gitcode.com/gh_mirrors/yi/yii2
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考