💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
这部分内容是专门为已经有Yii1.1基础的读者朋友写的。将Yii2.0与Yii1.1的不同点着重写出来,对比学起来会快得多。 而对于从未接触过Yii的读者朋友,这部分内容扫一扫就可以了,作为对过往历史的一个了解就够了。 如果有的内容你一时没看明白,也不要紧,本书的正文部分会讲清楚的。 另外,没有Yii1.1的经验,并不妨碍对Yii2.0的学习。 Yii官方有专门的文档归纳总结1.1版本和2.0版本的不同。以下内容,主要来自于官方的文档,我做了下精简, 选择比较重要的变化,并加入了一些个人的经验。 ## PHP新特性[](http://www.digpage.com/improvement.html#php "Permalink to this headline") 从对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](http://php.net/manual/en/book.spl.php) * 延迟静态绑定, 具体可以查看 [Late Static Bindings](http://php.net/manual/en/language.oop5.late-static-bindings.php) * [PHP标准日期时间](http://php.net/manual/en/book.datetime.php) * [特性(Traits)](http://php.net/manual/en/language.oop5.traits.php) * 使用PHP intl 扩展实现国际化支持, 具体可以查看 [PECL init](http://php.net/manual/en/book.intl.php) 。 了解Yii2.0使用了PHP的新特性,可以避免开发时由于环境不当,特别是开发生产环境切换时,产生莫名其妙的错误。 同时,也是让读者朋友借机学习PHP新知识的意思。 ## 命名空间(Namespace)[](http://www.digpage.com/improvement.html#namespace "Permalink to this headline") Yii2.0与Yii1.1之间最显著的不同是对于PHP命名空间的使用。Yii1.1中没有命名空间一说, 为避免Yii核心类与用户自定义类的命名冲突,所有的Yii核心类的命名,均冠以 C 前缀,以示区别。 而Yii2.0中所有核心类都使用了命名空间,因此, C 前缀也就人老珠黄,退出历史舞台了。 命名空间与实际路径相关联,比如 yii\base\Object 对应Yii目录下的 base/Object.php 文件。 ## 基础类[](http://www.digpage.com/improvement.html#id2 "Permalink to this headline") 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)[](http://www.digpage.com/improvement.html#event "Permalink to this headline") 在Yii1.1中,通过一个 on 前缀的方法来创建事件,比如 CActiveRecord 中的 onBeforeSave() 。 在Yii2.0中,可以任意定义事件的名称,并自己触发它: ~~~ $event = new \yii\base\Event; // 使用 trigger() 触发事件 $component->trigger($eventName, $event); // 使用 on() 前事件handler与对象绑定 $component->on($eventName, $handler); // 使用 off() 解除绑定 $component->off($eventName, $handler); ~~~ ## 别名(Alias)[](http://www.digpage.com/improvement.html#alias "Permalink to this headline") Yii2.0中改变了Yii1.1中别名的使用形式,并扩大了别名的范畴。 Yii1.1中,别名以 . 的形式使用: ~~~ RootAlias.path.to.target ~~~ 而在Yii2.0中,别名以 @ 前缀的方式使用: ~~~ @yii/jui ~~~ 另外,Yii2.0中,不仅有路径别名,还有URL别名: ~~~ // 路径别名 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)_](http://www.digpage.com/aliases.html#aliases) 。 ## 视图(View)[](http://www.digpage.com/improvement.html#view "Permalink to this headline") 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)[](http://www.digpage.com/improvement.html#model "Permalink to this headline") 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: ~~~ // 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中,这个错误的场景并没有任何的提示。假设有以下情况: ~~~ 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会有相应的提示, 这避免了上面这个低级错误的可能: ~~~ 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)[](http://www.digpage.com/improvement.html#controller "Permalink to this headline") 除了上面讲到的控制器中要使用 echo 来显示渲染视图的输出这点区别外, Yii1.1与Yii2.0的控制器还表现出更为明显的区别,那就是动作过滤器(Action Filter) 的不同。 在Yii2.0中,动作过滤器以行为(behavior)的方式出现, 一般继承自 yii\base\ActionFilter ,并注入到一个控制器中,以发生作用。比如,Yii1.1中很常见的: ~~~ public function behaviors() { return [ 'access' => [ 'class' => 'yii\filters\AccessControl', 'rules' => [ ['allow' => true, 'actions' => ['admin'], 'roles' => ['@']], ], ], ]; } ~~~ 看着是不是有点像,但又确实不一样? ## Active Record[](http://www.digpage.com/improvement.html#active-record "Permalink to this headline") 还记得么?在Yii1.1中,数据库查询被分散成 CDbCommand , CDbCriteria 和 CDbCommandBuilder 。 所谓天下大势分久必合,到了Yii2.0,采用 yii\db\Query 来表示数据库查询: ~~~ $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 关联关系: ~~~ class Customer extends \yii\db\ActiveRecord { ... ... public function getOrders() { // 关联的依据是 Order.customer_id = Customer.id return $this->hasMany('Order', ['customer_id' => 'id']); } } ~~~ 这样的话,可以通过 Customer 访问关联的 Order ~~~ // 获取所有与当前 $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)_](http://www.digpage.com/behavior.html#behavior) 的有关内容。 Yii2.0中,ActiveRecord得到极大的加强,在相关的章节中我们已经进行专门的讲解。 这里的内容主要是点一点Yii2.0之于Yii1.1的变化。大致了解下就可以了, 主要还是要看正文专门针对每个知识点的深入讲解。 如果觉得《深入理解Yii2.0》对您有所帮助,也请[帮助《深入理解Yii2.0》](http://www.digpage.com/donate.html#donate)。 谢谢!