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中,别名以 . 的形式使用:
RootAlias.path.to.target
而在Yii2.0中,别名以 @ 前缀的方式使用:
@yii/jui
另外,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
echo $this->render('_item', ['item' => $item]);
如果有一天你发现怎么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的变化。大致了解下就可以了, 主要还是要看正文专门针对每个知识点的深入讲解。