深入理解Yii2.0 附录解读


附录1:Yii2.0 对比 Yii1.1 的重大改进、附录2:Yii的安装


附录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新特性,主要有:

了解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中,可以任意定义事件的名称,并自己触发它:

$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别名:

// 路径别名
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:

// 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)

除了上面讲到的控制器中要使用 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

还记得么?在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) 的有关内容。

Yii2.0中,ActiveRecord得到极大的加强,在相关的章节中我们已经进行专门的讲解。

这里的内容主要是点一点Yii2.0之于Yii1.1的变化。大致了解下就可以了, 主要还是要看正文专门针对每个知识点的深入讲解。

附录2:Yii的安装

由于Yii2.0使用了许多PHP的新特性,因此,Yii需要PHP5.4.0以上版本。

有两种方法可以安装Yii,一种是使用Composer,另一种是直接下载压缩包。

使用Composer安装Yii

官方推荐使用Composer安装Yii。这样更方便后期维护,如果需要添加新的扩展或者升级Yii,只要一句命令就OK了。

用过Yii1.1的读者可能还有印象,安装Yii和构建应用是两个步骤:安装Yii,运行Yii搭建应用框架。 但是,如果使用Composer方法安装Yii,安装和构建应用是一步完成了。

这里并不打算介绍Composer的用法,这方面的内容通过官方文档或者搜索引擎都可以找到。 使用Composer安装Yii,也有两种选择:使用基本模版或者高级模版。 这两者最主要的区别在于高级模版提供了环境切换和前后台分离。 对于团队开发而言,环境切换功能很实用。对于大型应用,前后台分离既是逻辑上的划分,也是安全上的需要。 高级模版功能相对丰富,因此,本书在大多数情况下,都使用高级模版所创建应用进行讲解。 至于基本模版,原理上是相通的。

Yii2.0要求Composer必须安装 composer asset 插件。 这个插件使得Composer可以兼容实现NPM和BOWER包管理器的功能。 NPM 和 BOWER 主要用于前端资源(如JS库等)的管理。

# 安装Composer,如果已经安装过,可不必再安装
# curl -s http://getcomposer.org/installer | php

# 对于已经安装过Composer的,可以对其进行更新
php ../composer.phar self-update

# 为Composer 安装 composer asset 插件 (yii2 的 2.0.12 版本后换成了 Asset Packagist 插件,不需要安装 fxp/composer-asset-plugin 了)
php ../composer.phar global require "fxp/composer-asset-plugin:1.0.0-beta2"

# 使用高级模版安装Yii应用到 digpage.com 目录下
php ../composer.phar create-project --prefer-dist yiisoft/yii2-app-advanced webname

# 使用基础模版安装
# composer create-project --prefer-dist yiisoft/yii2-app-basic webname

如果想使用最新的开发版本的Yii基础模版,可加入 --stability=dev 参数。

从压缩包安装

  1. 从yiiframework.com下载最新的压缩包。
  2. 将压缩包解压缩到 /path/to/digpage.com 目录。
  3. 修改 config/web.php 文件,输入 cookieValidationKey 配置项密钥。 这个密钥主要用于cookie验证。 如果使用Composer安装,则Composer会自动设置一个密钥:
// !!! insert a secret key in the following (if it is empty) -
// this is required by cookie validation
'cookieValidationKey' => 'enter your secret key here',

设置Web服务器

常用的Web服务器有nginx + php-fpm 和 Apache。而且从趋势上看,前者的比重正不断提高。

本教程不打算介绍nginx和Apache的配置安装,这些内容从官方文档和搜索引擎都可以找到相关内容。 这里大体上介绍如何配置Web服务器,使其能够让Yii跑起来。

由于高级模版应用具有前台和后台。 一般来讲,前台和后台分离可以使用不同的主机名、端口,或者使用不同的路径名。使用不同的主机名的,如:

http://frontend.example.com/
http://backend.example.com/

使用不同的路径名的,如:

http://www.example.com/frontend/
http://www.example.com/backend/

无论使用何种方式,目的都是分离前台和后台。

由于是在本地开发,我们使用不同的端口来分离前台和后台。 体现在服务器上,采不同主机名、端口进行的分离方式,意味着不同的虚拟主机, 甚至是不同的物理服务器。 而不同的路径名的,则表现为同一台主机,不同目录。 这里,我们使用不同的端口来区别前后台,但在物理上, 前端和后端都部署在同一台服务器上,也就是使用虚拟主机。

使用Nginx的配置如下:

# for frontend
server {
    charset utf-8;
    client_max_body_size 128M;

    listen 80; ## listen for ipv4

    server_name localhost;
    root        /path/to/digpage.com/frontend/web;
    index       index.php;

    access_log  /path/to/digpage.com/frontend/log/access.log main;
    error_log   /path/to/digpage.com/frontend/log/error.log;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include fastcgi.conf;
        fastcgi_pass   127.0.0.1:9000;
    }

    location ~ /\.(ht|svn|git) {
        deny all;
    }
}

# for backend
server {
    # ...other settings...
    listen 81;
    server_name localhost;
    root /path/to/digpage.com/backend/web;
    # ...other settings...
}

如果使用Apache,也是分前台和后前的配置,以前台为例:

DocumentRoot "path/to/digpage.com/frontend/web"

<Directory "path/to/digpage.com/frontend/web">
    RewriteEngine on

    # If a directory or a file exists, use the request directly
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    # Otherwise forward the request to index.php
    RewriteRule . index.php

    # ...other settings...
</Directory>

对于后台,也是设置一个虚拟机,路径改为 path/to/digpage.com/backend/web 即可。

在linux下安装nginx后配置项目,我们可以在项目放到/var/www/目录下,然后在这个目录下新建一个目录vhost用于存放项目访问的nginx配置文件。 我们修改nginx对项目的根配置文件/etc/nginx/nginx.conf如下:

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 768;
    # multi_accept on;
}

http {

    ##
    # Basic Settings
    ##

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    # server_tokens off;

    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ##
    # SSL Settings
    ##

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    ##
    # Logging Settings
    ##

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    ##
    # Gzip Settings
    ##

    gzip on;
    gzip_disable "msie6";

    # gzip_vary on;
    # gzip_proxied any;
    # gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
    include /var/www/vhost/*.conf;
}


#mail {
#    # See sample authentication script at:
#    # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
# 
#    # auth_http localhost/auth.php;
#    # pop3_capabilities "TOP" "USER";
#    # imap_capabilities "IMAP4rev1" "UIDPLUS";
# 
#    server {
#        listen     localhost:110;
#        protocol   pop3;
#        proxy      on;
#    }
# 
#    server {
#        listen     localhost:143;
#        protocol   imap;
#        proxy      on;
#    }
#}

其实就是增加了这一行:

include /var/www/vhost/*.conf;

然后我们看一下一个项目配置/var/www/vhost/admin.com.conf的例子:

server {
    charset utf-8;
    client_max_body_size 128M;

    listen 80; ## listen for ipv4
    #listen [::]:80 default_server ipv6only=on; ## listen for ipv6

    server_name admin.com;
    root        /var/www/Admin/admin/web;
    index       index.php;

    access_log  /var/www/log/access/admin.com.log;
    error_log   /var/www/log/error/admin.com.log;

    location / {
        # Redirect everything that isn't a real file to index.php
        try_files $uri $uri/ /index.php$is_args$args;
    }

    # uncomment to avoid processing of calls to non-existing static files by Yii
    #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
    #    try_files $uri =404;
    #}
    #error_page 404 /404.html;

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        #fastcgi_pass 127.0.0.1:9000;
        fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        try_files $uri =404;
    }

    location ~* /\. {
        deny all;
    }
}

Yii中的前后台

Yii从来不认得什么是前台,什么是后台。从本质来讲,前台和后台都是应用。 换句话说,你可以先用基本模板开发出一个应用, 具有前台的全部功能,然后部署。 如法炮制另一个独立的,具有后台全部功能的应用。 这在原理上是一样一样的。 只是,按照我们的经验,前台和后台从逻辑上讲,组成一应用比较符合我们的认知。 而且,从代码复用的角度来讲, 我们更希望前台和后台的代码可以互通互用,尽量不要重复造轮子。 但是,对于Yii来讲,前台就是一个完备的应用, 后台又是另一个应用。这点区别请读者朋友们留意。

那么Yii的高级模版是如何实现把两个应用整合成一个我们认识上的应用的呢。 我们观察一下/path/to/digpage.com/ 目录, 看看这个高级模版是如何组织代码的。不难发现其中有二个目录frontend backend 分别代表了前台、后台。

其实你把 frontend backend 中的任意1个目录删除,是不影响剩下的目录的正常运转的。 也就是说,他们相互间是独立的。只不过在代码组织上,他们都放在了 /path/to/application/ 目录下面。

如果深入下去,你会发现不光 frontend backend 其实 console 也是一个完备的Yii应用, 它通常用于维护,是个命令行应用。

总的说,Yii的前台、后台什么的,是我们命名的概念,他们都是独立而完备的应用。 同时,他们又都具有一定的联系, 这些联系突出体现在了 common 目录上。 这个目录从字面的意思看,就是通用,对于组织到一起的 frontend backend console 而言, common 中的内容,他们都可以使用。这是Yii中实现代码复用的技巧所在。

更多关于Yii应用的目录结构的内容,请看 Yii应用的目录结构和入口脚本 部分。

配置应用环境

还差最后一步就完成Yii的安装了。这最后一步,就是设置应用环境。在Yii的高级模版应用中,引入了环境的概念。

环境就是指开发环境、测试环境、产品环境等。 对于Yii而言,所谓的环境,就是一组与运行环境相关的配置文件和入口脚本。

Yii对于环境的使用是这样一个原理:采用一个自动化的脚本,每次需要切换环境时,就运行脚本, 由开发者确定要采用何种环境, 然后将对应环境的所有配置文件都覆盖当前的配置文件。 在Yii中,与环境相关的文件其实就只有两个:一个是入口脚本 index.php,另一个就是各类配置文件。

在切换环境时,只需要一行命令就全部搞定:

php /path/to/webname/init

这行命令会提示你选择何种开发环境,并确认是否覆盖当前的配置文件。下面是输出的内容:

Yii Application Initialization Tool v1.0

Which environment do you want the application to be initialized in?

  [0] Development
  [1] Production

  Your choice [0-1, or "q" to quit] 0    // 这里选择了 Development 环境

  Initialize the application under 'Development' environment? [yes|no] yes

  Start initialization ...

   generate yii
   generate common/config/main-local.php
   generate common/config/params-local.php
   generate backend/config/main-local.php
   generate backend/config/params-local.php
   generate backend/web/index.php
   generate backend/web/index-test.php
   generate frontend/config/main-local.php
   generate frontend/config/params-local.php
   generate frontend/web/index.php
   generate frontend/web/index-test.php
   generate console/config/main-local.php
   generate console/config/params-local.php
   generate cookie validation key in backend/config/main-local.php
   generate cookie validation key in frontend/config/main-local.php
      chmod 0777 backend/runtime
      chmod 0777 backend/web/assets
      chmod 0777 frontend/runtime
      chmod 0777 frontend/web/assets
      chmod 0755 yii

  ... initialization completed.

从上面的输出可以看出来, init 脚本其实做了3件事:

  • 复制文件到相应位置,覆盖当前配置。
  • 生成 cookieValidationKey 并写入相应文件。
  • 设置相关文件和目录的权限。

如果想更加便捷,可以直接指定相关的参数:

php /path/to/yii-application/init --env=Production overwrite=All

第二种方式直接在命令行中指明使用的环境,并要求全部覆盖当前配置文件。

检验安装情况

我们已经完成了Yii的安装,可能使用了Composer,也可能使用了压缩包。 接着,我们配置好了Web服务器。 最后,我们运行了 init 命令。 那么,Yii应用的基本框架就已经搭建好了。你可在你的浏览器中试试效果。 使用 http://localhost:80/ 可以打开网站前台,使用 http://localhost:81 可以打开后台。

附图






参考资料

深入理解Yii2.0 » 附录 » 附录1:Yii2.0 对比 Yii1.1 的重大改进

http://www.digpage.com/improvement.html

https://www.kancloud.cn/kancloud/yii-in-depth/50773

深入理解Yii2.0 » 附录 » 附录2:Yii的安装

http://www.digpage.com/install.html

https://www.kancloud.cn/kancloud/yii-in-depth/50774


返回