暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Yii2教程

原创 yBmZlQzJ 2023-08-16
1172

Yii2教程

Yii 是什么

===========Yii 是一个高性能,基于组件的 PHP 框架,用于快速开发现代 Web 应用程序。名字 Yii (读作 易)在中文里有 “极致简单与不断演变” 两重含义,也可看作 Yes It Is! 的缩写。

Yii 最适合做什么?

Yii 是一个通用的 Web 编程框架,即可以用于开发各种基于 PHP 的 Web 应用。因为基于组件的框架结构和设计精巧的缓存支持,Yii 特别适合开发大型应用,如门户网站、论坛、内容管理系统(CMS)、电子商务项目和 RESTful Web 服务等。

Yii 和其他框架相比呢?

  • 和其他 PHP 框架类似,Yii 实现了 MVC(Model-View-Controller)设计模式并基于该模式组织代码。- Yii 的代码简洁优雅,这是 Yii 的编程哲学。它永远不会为了要迎合某个设计模式而对代码进行过度的设计。- Yii 是一个全栈框架,提供了大量久经考验,开箱即用的特性,例如:对关系型和 NoSQL 数据库都提供了查询生成器(QueryBuilders)和 ActiveRecord;RESTful API 的开发支持;多层缓存支持,等等。- Yii 非常易于扩展。你可以自定义或替换几乎任何一处核心代码。你还会受益于它坚实可靠的扩展架构,使用、再开发或再发布扩展。- 高性能始终是 Yii 的首要目标之一。Yii 不是一场独角戏,它由一个强大的开发者团队提供支持,也有一个庞大的专家社区,持续不断地对 Yii 的开发作出贡献。Yii 开发者团队始终对 Web 开发最新潮流和其他框架及项目中的最佳实践和特性保持密切关注,那些有意义的最佳实践及特性会被不定期的整合进核心框架中,并提供简单优雅的接口。

Yii 版本

Yii 当前有两个主要版本:1.1 和 2.0。 1.1 版是上代的老版本,现在处于维护状态。2.0 版是一个完全重写的版本,采用了最新的技术和协议,包括依赖包管理器(Composer)、PHP 代码规范(PSR)、命名空间、Traits(特质)等等。 2.0 版代表了最新一代框架,是未来几年中我们的主要开发版本。本指南主要基于 2.0 版编写。

系统要求和先决条件

Yii 2.0 需要 PHP 5.4.0 或以上版本支持。你可以通过运行任何 Yii 发行包中附带的系统要求检查器查看每个具体特性所需的 PHP 配置。使用 Yii 需要对面向对象编程(OOP)有基本了解,因为 Yii 是一个纯面向对象的框架。Yii 2.0 还使用了 PHP 的最新特性,例如 命名空间Trait(特质)。理解这些概念将有助于你更快地掌握 Yii 2.0。

从 Yii 1.1升级到 Yii2 - Yii2教程

从 Yii 1.1 升级

2.0 版框架是完全重写的,在 1.1 和 2.0 两个版本之间存在相当多差异。因此从 1.1 版升级并不像小版本间的跨越那么简单,通过本指南你将会了解两个版本间主要的不同之处。如果你之前没有用过 Yii 1.1,可以跳过本章,直接从"入门篇"开始读起。请注意,Yii 2.0 引入了很多本章并没有涉及到的新功能。强烈建议你通读整部权威指南来了解所有新特性。这样有可能会发现一些以前你要自己开发的功能,而现在已经被包含在核心代码中了。

安装

Yii 2.0 完全拥抱 Composer,它是PHP中的一个依赖管理工具。核心框架以及扩展的安装都通过 Composer 来处理。想要了解更多如何安装 Yii 2.0 请参阅本指南的 安装 Yii 章节。如果你想创建新扩展,或者把你已有的 Yii 1.1 的扩展改写成兼容 2.0 的版本,你可以参考 创建扩展 章节。

PHP 需求

Yii 2.0 需要 PHP 5.4 或更高版本,该版本相对于 Yii 1.1 所需求的 PHP 5.2 而言有巨大的改进。因此在语言层面上有很多的值得注意的不同之处。下面是 PHP 层的主要变化汇总:- 命名空间- 匿名函数- 数组短语法 [...元素...] 用于取代 array(...元素...)- 视图文件中的短格式 echo 标签 <?=,自 PHP 5.4 起总会被识别并且合法,无论 short_open_tag 的设置是什么,可以安全使用。- SPL 类和接口- 延迟静态绑定- 日期和时间- Traits- intl Yii 2.0 使用 PHP 扩展 intl 来支持国际化的相关功能。

命名空间

Yii 2.0 里最明显的改动就数命名空间的使用了。几乎每一个核心类都引入了命名空间,比如 yii\web\Request。1.1 版用于类名前的字母 “C” 已经不再使用。当前的命名规范与目录结构相吻合。例如,yii\web\Request 就表明对应的类文件是 Yii 框架文件夹下的 web/Request.php 文件。(有了 Yii 的类自动加载器,你可以直接使用全部核心类而不需要显式包含具体文件。)

组件(Component)与对象(Object)

Yii 2.0 把 1.1 里的 CComponent 类拆分成了两个类:yii\base\Object 和 yii\base\Component。yii\base\Object|Object 类是一个轻量级的基类,你可以通过 getters 和 setters 来定义 object 的属性。yii\base\Component|Component 类继承自 yii\base\Object|Object,同时进一步支持 事件 和 行为。如果你不需要用到事件或行为,应该考虑使用 yii\base\Object|Object 类作为基类。这通常是表示基本数据结构的类。

对象的配置

yii\base\Object|Object 类引入了一种统一对象配置的方法。所有 yii\base\Object|Object 的子类都应该用以下方法声明它的构造方法(如果需要的话),以正确配置它自身:

class MyClass extends \yii\base\Object
{
public function __construct($param1, $param2, $config = [])
{
// ... 配置生效前的初始化过程

parent::__construct($config);
}

public function init()
{
parent::init();

// ...配置生效后的初始化过程
}
}

在上面的例子里,构造方法的最后一个参数必须输入一个配置数组,包含一系列用于在方法结尾初始化相关属性的键值对。你可以重写 yii\base\Object::init()|init() 方法来执行一些需要在配置生效后进行的初始化工作。你可以通过遵循以下约定俗成的编码习惯,来使用配置数组创建并配置新的对象:

$object = Yii::createObject([
'class' => 'MyClass',
'property1' => 'abc',
'property2' => 'cde',
], [$param1, $param2]);

更多有关配置的细节可以在配置章节找到。

事件(Event)

在 Yii 1 中,通常通过定义 on 开头的方法(例如 onBeforeSave)来创建事件。而在 Yii 2 中,你可以使用任意的事件名了。同时通过调用 yii\base\Component::trigger()|trigger() 方法来触发相关事件:

$event = new \yii\base\Event;
$component->trigger($eventName, $event);

要给事件附加一个事件句柄(Event Handler 或者叫事件处理器),需要使用 yii\base\Component::on()|on() 方法:

$component->on($eventName, $handler);
// 要解除相关句柄,使用 off 方法:
// $component->off($eventName, $handler);

事件功能还有更多增强之处。要了解它们,请查看事件章节。

路径别名(Path Alias)

Yii 2.0 将路径别名的应用扩大至文件/目录路径和 URL。Yii 2.0 中路径别名必须以 @ 符号开头,以区别于普通文件目录路径或 URL。例如 @yii 就是指向 Yii 安装目录的别名。绝大多数 Yii 核心代码都支持别名。例如 yii\caching\FileCache::cachePath 就同时支持路径别名或普通的目录地址。路径别名也和类的命名空间密切相关。建议给每一个根命名空间定义一个路径别名,从而无须额外配置,便可启动 Yii 的类自动加载机制。例如,因为有 @yii 指向 Yii 安装目录,那类似 yii\web\Request 的类就能被 Yii 自动加载。同理,若你用了一个第三方的类库,如 Zend Framework,你只需定义一个名为 @Zend 的路径别名指向该框架的安装目录。之后 Yii 就可以自动加载任意 Zend Framework 中的类了。更多路径别名信息请参阅路径别名章节。

视图(View)

Yii 2 中视图最明显的改动是视图内的特殊变量 $this 不再指向当前控制器或小部件,而是指向视图对象,它是 2.0 中引入的全新概念。视图对象为 yii\web\View 的实例,他代表了 MVC 模式中的视图部分。如果你想要在视图中访问一个控制器或小部件,可以使用 $this->context。要在其他视图里渲染一个局部视图,使用 $this->render(),而不是 $this->renderPartial()。render() 现在只返回渲染结果,而不是直接显示它,所以现在你必须显式地把它 echo 出来。像这样:

echo $this->render('_item', ['item' => $item]);

除了使用 PHP 作为主要的模板语言,Yii 2.0 也装备了两种流行模板引擎的官方支持:Smarty 和 Twig。过去的 Prado 模板引擎不再被支持。要使用这些模板引擎,你需要配置 view 应用组件,给它设置 yii\base\View::$renderers|View::$renderers 属性。具体请参阅模板引擎章节。

模型(Model)

Yii 2.0 使用 yii\base\Model 作为模型基类,类似于 1.1 的 CModel 。CFormModel 被完全弃用了,现在要创建表单模型类,可以通过继承 yii\base\Model 类来实现。Yii 2.0 引进了名为 yii\base\Model::scenarios()|scenarios() 的新方法来声明支持的场景,并指明在哪个场景下某属性必须经过验证,可否被视为安全值等等。如:

public function scenarios()
{
return [
'backend' => ['email', 'role'],
'frontend' => ['email', '!role'],
];
}

上面的代码声明了两个场景:backend 和 frontend 。对于 backend 场景,email 和 role 属性值都是安全的,且能进行批量赋值。对于 frontend 场景,email 能批量赋值而 role 不能。 email 和 role 都必须通过规则验证。yii\base\Model::rules()|rules() 方法仍用于声明验证规则。注意,由于引入了 yii\base\Model::scenarios()|scenarios(),现在已经没有 unsafe 验证器了。大多数情况下,如果 yii\base\Model::rules()|rules() 方法内已经完整地指定场景了,那就不必覆写 yii\base\Model::scenarios()|scenarios(),也不必声明 unsafe 属性值。要了解更多有关模型的细节,请参考模型章节。

控制器(Controller)

Yii 2.0 使用 yii\web\Controller 作为控制器的基类,类似于 1.1 的 CWebController。使用 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;
}
}

请查看 控制器(Controller) 章节了解有关控制器的更多细节。

小部件(Widget)

Yii 2.0 使用 yii\base\Widget 作为小部件基类,类似于 1.1 的 CWidget。为了让框架获得更好的 IDE 支持,Yii 2.0 引进了一个调用小部件的新语法。包含 yii\base\Widget::begin()|begin(),yii\base\Widget::end()|end() 和 yii\base\Widget::widget()|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();

更多细节请参阅小部件章节。

主题(Theme)

2.0 主题的运作方式跟以往完全不同了。它们现在基于路径映射机制,该机制会把一个源视图文件的路径映射到一个主题视图文件路径。举例来说,如果路径映射为 ['/web/views' => '/web/themes/basic'],那么 /web/views/site/index.php 视图经过主题修饰的版本就会是 /web/themes/basic/site/index.php。也因此让主题现在可以应用在任何视图文件之上,甚至是渲染控制器上下文环境之外的视图文件或小部件。同样,CThemeManager 组件已经被移除了。取而代之的 theme 成为了 view 应用组件的一个可配置属性。更多细节请参考主题章节。

控制台应用(Console Application)

控制台应用现在如普通的 Web 应用程序一样,由控制器组成,控制台的控制器继承自 yii\console\Controller,类似于 1.1 的 CConsoleCommand。运行控制台命令使用 yii <route>,其中 <route> 代表控制器的路由(如 sitemap/index)。额外的匿名参数传递到对应的控制器操作方法,而有名的参数根据 yii\console\Controller::options() 的声明来解析。Yii 2.0 支持基于代码注释自动生成相的关命令行帮助(help)信息。更多细节请参阅控制台命令章节。

国际化(I18N)

Yii 2.0 移除了原来内置的日期格式器和数字格式器,为了支持 PECL intl PHP module(PHP 的国际化扩展)的使用。消息翻译现在由 i18n 应用组件执行。该组件管理一系列消息源,允许使用基于消息类别的不同消息源。更多细节请参阅国际化(Internationalization)章节。

操作过滤器(Action Filters)

操作的过滤现在通过行为(behavior)来实现。要定义一个新的,自定义的过滤器,请继承 yii\base\ActionFilter 类。要使用一个过滤器,需要把过滤器类作为一个 behavior 绑定到控制器上。例如,要使用 yii\filters\AccessControl 过滤器,你需要在控制器内添加如下代码:

public function behaviors()
{
return [
'access' => [
'class' => 'yii\filters\AccessControl',
'rules' => [
['allow' => true, 'actions' => ['admin'], 'roles' => ['@'`,
],
],
];
}

更多细节请参考过滤器章节。

前端资源(Assets)

Yii 2.0 引入了一个新的概念,称为资源包(Asset Bundle),以代替 1.1 的脚本包概念。一个资源包是一个目录下的资源文件集合(如 JavaScript 文件、CSS 文件、图片文件等)。每一个资源包被表示为一个类,该类继承自 yii\web\AssetBundle。用 yii\web\AssetBundle::register() 方法注册一个资源包后,就使它的资源可被 Web 访问了,注册了资源包的页面会自动包含和引用资源包内指定的 JS 和 CSS 文件。更多细节请参阅 前端资源管理(Asset) 章节。

助手类(Helpers)

Yii 2.0 很多常用的静态助手类,包括: yii\helpers\Html yii\helpers\ArrayHelper yii\helpers\StringHelper yii\helpers\FileHelper* yii\helpers\Json请参考助手一览 章节来了解更多。

表单

Yii 2.0 引进了表单栏(field)的概念,用来创建一个基于 yii\widgets\ActiveForm的表单。一个表单栏是一个由标签、输入框、错误消息(可能还有提示文字)组成的容器,被表示为 yii\widgets\ActiveField|ActiveField 对象。使用表单栏建立表单的过程比以前更整洁利落:


<?php field($model, 'username') ?>
<?php field($model, 'password')->passwordInput() ?>

请参考创建表单章节来了解更多细节。

查询生成器(Query Builder)

Yii 1.1 中,查询语句的生成分散在多个类中,包括 CDbCommand,CDbCriteria 以及 CDbCommandBuilder。Yii 2.0 以 yii\db\Query|Query 对象的形式表示一个数据库查询,这个对象使用 yii\db\QueryBuilder|QueryBuilder 在幕后生成 SQL 语句。例如:

$query = new \yii\db\Query();
$query->select('id, name')
->from('user')
->limit(10);

$command = $query->createCommand();
$sql = $command->sql;
$rows = $command->queryAll();

最重要的是,这些查询生成方法还可以和活动记录配合使用。请参考查询生成器(Query Builder)章节了解更多内容。

活动记录(Active Record)

Yii 2.0 的活动记录改动了很多。两个最显而易见的改动分别涉及查询语句的生成(query building)和关联查询的处理(relational query handling)。1.1 中的 CDbCriteria 类在 Yii 2 中被 yii\db\ActiveQuery 所替代。这个类是继承自 yii\db\Query,因此也继承了所有查询生成方法。开始拼装一个查询可以调用 yii\db\ActiveRecord::find() 方法进行:

// 检索所有 *活动的* 客户和订单,并以 ID 排序:
$customers = Customer::find()
->where(['status' => $active])
->orderBy('id')
->all();

要声明一个关联关系,只需简单地定义一个 getter 方法来返回一个 yii\db\ActiveQuery|ActiveQuery 对象。getter 方法定义的属性名(译者注:即 getOrders() 中的 orders)表示关联关系名。如,以下代码声明了一个名为 orders 的关系(1.1 中必须在 relations() 方法内声明关系):

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();

当贪婪加载一段关联关系时,Yii 2.0 和 1.1 的运作机理并不相同。具体来说,在 1.1 中使用一条 JOIN 语句同时查询主表和关联表记录。在 Yii 2.0 中会使用两个没有 JOIN 的 SQL 语句:第一条语句取回主表记录,第二条通过主表记录经主键筛选后查询关联表记录。当生成返回大量记录的查询时,可以链式书写 yii\db\ActiveQuery::asArray()|asArray() 方法,这样会以数组的形式返回查询结果,而不必返回yii\db\ActiveRecord|ActiveRecord 对象,这能显著降低因大量记录读取所消耗的 CPU 时间和内存。如:

$customers = Customer::find()->asArray()->all();

另一个改变是你不能再通过公共数据定属性(Attribute)的默认值了。如果你需要这么做的话,可以在你的记录类的 init 方法中设置它们。

public function init()
{
parent::init();
$this->status = self::STATUS_NEW;
}

曾几何时,在 1.1 中重写一个活动记录类的构造方法(Constructor)会导致一些问题。它们不会在 2.0 中出现了。需要注意的是,如果你需要在构造方法中添加一些参数,恐怕必须重写 yii\db\ActiveRecord::instantiate() 方法。活动记录方面还有很多其他的变化与改进,请参考活动记录章节以了解更多细节。

用户及身份验证接口(IdentityInterface)

1.1 中的 CWebUser 类现在被 yii\web\User 所取代,随之 CUserIdentity 类也不在了。与之相对的,为达到相同目的,你可以实现 yii\web\IdentityInterface 接口,它使用起来更直观。在高级应用模版里提供了一个这样的一个例子。要了解更多细节请参考认证(Authentication),授权(Authorization)以及高级应用模版 这三个章节。

URL 管理

Yii 2.0 的 URL 管理跟 1.1 中很像。一个主要的改进是现在的 URL 管理支持可选参数了。比如,如果你在 2.0 中定义了一个下面这样的规则,那么它可以同时匹配 post/popular 和 post/1/popular 两种 URL。而在 1.1 中为达成相同效果,必须要使用两条规则。

[
'pattern' => 'post/<page:\d+>/<tag>',
'route' => 'post/index',
'defaults' => ['page' => 1],
]</tag></page:\d+>

请参考URL 解析和生成 章节,以了解更多细节。.

同时使用 Yii 1.1 和 2.x

如果你有一些遗留的 Yii 1.1 代码,需要跟 Yii 2.0 一起使用,可以参考 1.1 和 2.0 共用章节。

安装Yii2 - Yii2教程

安装 Yii==============你可以通过两种方式安装 Yii:使用 Composer 或下载一个归档文件。推荐使用前者,这样只需执行一条简单的命令就可以安装新的扩展或更新 Yii 了。> 注意:和 Yii 1 不同,以标准方式安装 Yii 2 时会同时下载并安装框架本身和一个应用程序的基本骨架。通过 Composer 安装 -----------------------如果还没有安装 Composer,你可以按 getcomposer.org 中的方法安装。在 Linux 和 Mac OS X 中可以运行如下命令:

curl -s http://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

在 Windows 中,你需要下载并运行 Composer-Setup.exe。如果遇到任何问题或者想更深入地学习 Composer,请参考 Composer 文档(英文)Composer 中文。Composer 安装后,切换到一个可通过 Web 访问的目录,执行如下命令即可安装 Yii :

composer create-project --prefer-dist yiisoft/yii2-app-basic basic

如上命令会将 Yii 安装在名为 basic 的目录中。> 技巧:如果你想安装 Yii 的最新开发版本,可以使用如下命令,它添加了一个 stability 选项(中文版):

composer create-project --prefer-dist --stability=dev yiisoft/yii2-app-basic basic

注意,Yii 的开发版(dev 版)不应该用于生产环境中,它可能会破坏运行中的代码。通过归档文件安装 -------------------------------通过归档文件安装 Yii 包括两个步骤:1. 从 yiiframework.com 下载归档文件。2. 将下载的文件解压缩到 Web 目录中。3. 修改 config/web.php 文件,给 cookieValidationKey 配置项添加一个密钥(若你通过 Composer 安装,则此步骤会自动完成): php // !!! 在下面插入一段密钥(若为空) - 以供 cookie validation 的需要 'cookieValidationKey' => '在此处输入你的密钥',其他安装方式 --------------------------上文介绍了两种安装 Yii 的方法,安装的同时也会创建一个立即可用的 Web 应用程序。对于小的项目或用于学习上手,这都是一个不错的起点。但是其他的安装方式也存在: 如果你只想安装核心框架,然后从零开始构建整个属于你自己的应用程序模版,可以参考从头构建自定义模版一节的介绍。 如果你要开发一个更复杂的应用,可以更好地适用于团队开发环境的,可以考虑安装高级应用模版。验证安装的结果 --------------------------安装完成后,就可以使用浏览器通过如下 URL 访问刚安装完的 Yii 应用了:

http://localhost/basic/web/index.php

这个 URL 假设你将 Yii 安装到了一个位于 Web 文档根目录下的 basic 目录中,且该 Web 服务器正运行在你自己的电脑上(localhost)。你可能需要将其调整为适应自己的安装环境。![Yii 安装成功]你应该可以在浏览器中看到如上所示的 “Congratulations!” 页面。如果没有,请通过以下任意一种方式,检查当前 PHP 环境是否满足 Yii 最基本需求: 通过浏览器访问 URL http://localhost/basic/requirements.php 执行如下命令:

http://localhost/basic/web/index.php

你需要配置好 PHP 安装环境,使其符合 Yii 的最小需求。主要是需要 PHP 5.4 以上版本。如果应用需要用到数据库,那还要安装 PDO PHP 扩展 和相应的数据库驱动(例如访问 MySQL 数据库所需的 pdo_mysql)。配置 Web 服务器----------------------->补充:如果你现在只是要试用 Yii 而不是将其部署到生产环境中,本小节可以跳过。通过上述方法安装的应用程序在 Windows,Max OS X,Linux 中的 Apache HTTP 服务器Nginx HTTP 服务器 上都可以直接运行。在生产环境的服务器上,你可能会想配置服务器让应用程序可以通过 URL http://www.example.com/index.php 访问而不是 http://www.example.com/basic/web/index.php。这种配置需要将 Web 服务器的文档根目录指向 basic/web 目录。可能你还会想隐藏掉 URL 中的 index.php,具体细节在 URL 解析和生成 一章中有介绍,你将学到如何配置 Apache 或 Nginx 服务器实现这些目标。>补充:将 basic/web 设置为文档根目录,可以防止终端用户访问 basic/web 相邻目录中的私有应用代码和敏感数据文件。禁止对其他目录的访问是一个不错的安全改进。>补充:如果你的应用程序将来要运行在共享虚拟主机环境中,没有修改其 Web 服务器配置的权限,你依然可以通过调整应用的结构来提升安全性。详情请参考共享主机环境 一章。### 推荐使用的 Apache 配置 在 Apache 的 httpd.conf 文件或在一个虚拟主机配置文件中使用如下配置。注意,你应该将 path/to/basic/web 替换为实际的 basic/web 目录。

# 设置文档根目录为 “basic/web”
DocumentRoot "path/to/basic/web"
<directory basic="" path="" to="">RewriteEngine on

# 如果请求的是真实存在的文件或目录,直接访问
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# 如果请求的不是真实文件或目录,分发请求至 index.php
RewriteRule . index.php

# ...其它设置...</directory>

推荐使用的 Nginx 配置 为了使用 Nginx,你应该已经将 PHP 安装为 FPM SAPI 了。使用如下 Nginx 配置,将 path/to/basic/web 替换为实际的 basic/web 目录,mysite.local 替换为实际的主机名以提供服务。

server {
charset utf-8;
client_max_body_size 128M;

listen 80; ## 监听 ipv4 上的 80 端口
#listen [::]:80 default_server ipv6only=on; ## 监听 ipv6 上的 80 端口

server_name mysite.local;
root /path/to/basic/web;
index index.php;

access_log /path/to/basic/log/access.log main;
error_log /path/to/basic/log/error.log;

location / {
# 如果找不到真实存在的文件,把请求重定向给 index.php
try_files $uri $uri/ /index.php?$args;
}

# 若取消下面这段的注释,可避免 Yii 接管不存在文件的处理过程(404)
#location ~ .(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
# try_files $uri =404;
#}
#error_page 404 /404.htmll;

location ~ .php$ {
include fastcgi.conf;
fastcgi_pass 127.0.0.1:9000;
#fastcgi_pass unix:/var/run/php5-fpm.sock;
}

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

使用该配置时,你还应该在 php.ini 文件中设置 cgi.fix_pathinfo=0 ,能避免掉很多不必要的 stat() 系统调用。还要注意当运行一个 HTTPS 服务器时,需要添加 fastcgi_param HTTPS on; 一行,这样 Yii 才能正确地判断连接是否安全。

运行应用 - Yii2教程

运行应用

安装 Yii 后,就有了一个运行中的 Yii 应用,根据配置的不同,可以通过 http://hostname/basic/web/index.php 或 http://hostname/index.php 访问。本章节将介绍应用的内建功能,如何组织代码,以及一般情况下应用如何处理请求。 > 补充:为简单起见,在整个“入门”板块都假定你已经把 basic/web 设为 Web 服务器根目录并配置完毕,你访问应用的地址会是 http://lostname/index.php 或类似的。请按需调整 URL。

功能

一个安装完的基本应用包含四页: * 主页,当你访问 http://hostname/index.php 时显示, * “About” 页, * “Contact” 页, 显示一个联系表单,允许终端用户通过 Email 联系你, * “Login” 页, 显示一个登录表单,用来验证终端用户。试着用 “admin/admin” 登录,你可以看到当前是登录状态,已经可以“退出登录”了。 这些页面使用同一个头部和尾部。头部包含了一个可以在不同页面间切换的导航栏。 在浏览器底部可以看到一个工具栏。这是 Yii 提供的很有用的调试工具,可以记录并显示大量的调试信息,例如日志信息,响应状态,数据库查询等等。

应用结构

应用中最重要的目录和文件(假设应用根目录是 basic):

basic/ 应用根目录
composer.json Composer 配置文件, 描述包信息
config/ 包含应用配置及其它配置
console.php 控制台应用配置信息
web.php Web 应用配置信息
commands/ 包含控制台命令类
controllers/ 包含控制器类
models/ 包含模型类
runtime/ 包含 Yii 在运行时生成的文件,例如日志和缓存文件
vendor/ 包含已经安装的 Composer 包,包括 Yii 框架自身
views/ 包含视图文件
web/ Web 应用根目录,包含 Web 入口文件
assets/ 包含 Yii 发布的资源文件(javascript 和 css)
index.php 应用入口文件
yii Yii 控制台命令执行脚本

一般来说,应用中的文件可被分为两类:在 basic/web 下的和在其它目录下的。前者可以直接通过 HTTP 访问(例如浏览器),后者不能也不应该被直接访问。 Yii 实现了模型-视图-控制器 (MVC)设计模式,这点在上述目录结构中也得以体现。 models 目录包含了所有[模型类],views 目录包含了所有[视图脚本],controllers 目录包含了所有[控制器类]。 以下图表展示了一个应用的静态结构: [应用静态结构] 每个应用都有一个入口脚本 web/index.php,这是整个应用中唯一可以访问的 PHP 脚本。入口脚本接受一个 Web 请求并创建[应用]实例去处理它。 [应用]在它的[组建]辅助下解析请求,并分派请求至 MVC 元素。[视图]使用[小部件]去创建复杂和动态的用户界面。

请求生命周期

以下图表展示了一个应用如何处理请求: [请求生命周期] 1. 用户向[入口脚本]web/index.php 发起请求。 2. 入口脚本加载应用[配置]并创建一个[应用]实例去处理请求。 3. 应用通过[请求]组件解析请求的[路由]。 4. 应用创建一个[控制器]实例去处理请求。 5. 控制器创建一个[操作]实例并针对操作执行过滤器。 6. 如果任何一个过滤器返回失败,则操作退出。 7. 如果所有过滤器都通过,操作将被执行。 8. 操作会加载一个数据模型,或许是来自数据库。 9. 操作会渲染一个视图,把数据模型提供给它。 10. 渲染结果返回给[响应]组件。 11. 响应组件发送渲染结果给用户浏览器。

第一个程序HelloWord - Yii2教程

说声 Hello

本章节描述了如何在你的应用中创建一个新的 “Hello” 页面。为了做到这点,将会创建一个[操作]和一个[视图]: 应用将会分派页面请求给操作 操作将会依次渲染视图呈现 “Hello” 给最终用户 贯穿整个章节,你将会掌握三件事: 1. 如何创建一个[操作]去响应请求, 2. 如何创建一个[视图]去构造响应内容, 3. 以及一个应用如何分派请求给[操作]。

创建操作

为了说 “Hello”,需要创建一个 say [操作],从请求中接收 message 参数并显示给最终用户。如果请求没有提供 message 参数,操作将显示默认参数 “Hello”。 > 补充:[操作]是最终用户可以直接访问并执行的对象。操作被组织在[控制器]中。一个操作的执行结果就是最终用户收到的响应内容。 操作必须声明在[控制器]中。为了简单起见,你可以直接在 SiteController 控制器里声明 say 操作。这个控制器是由文件 controllers/SiteController.php 定义的。以下是一个操作的声明:

<?php
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
// ...其它代码...
public function actionSay($message = '你好')
{
return $this->render('say', ['message' => $message]);
}
}

在上述 SiteController 代码中,say 操作被定义为 actionSay 方法。Yii 使用 action 前缀区分普通方法和操作。action 前缀后面的名称被映射为操作的 ID。 涉及到给操作命名时,你应该理解 Yii 如何处理操作 ID。操作 ID 总是被以小写处理,如果一个操作 ID 由多个单词组成,单词之间将由连字符连接(如 create-comment)。操作 ID 映射为方法名时移除了连字符,将每个单词首字母大写,并加上 action 前缀。 例子:操作 ID create-comment 相当于方法名 actionCreateComment。 上述代码中的操作方法接受一个参数 $message,它的默认值是 “Hello”(就像你设置 PHP 中其它函数或方法的默认值一样)。当应用接收到请求并确定由 say 操作来响应请求时,应用将从请求的参数中寻找对应值传入进来。换句话说,如果请求包含一个 message 参数,它的值是 “Goodybye”, 操作方法中的 $message 变量也将被填充为 “Goodbye”。 在操作方法中,yii\web\Controller::render()|render() 被用来渲染一个名为 say 的视图文件。 message 参数也被传入视图,这样就可以在里面使用。操作方法会返回渲染结果。结果会被应用接收并显示给最终用户的浏览器(作为整页 HTML 的一部分)。

创建视图

[视图]是你用来生成响应内容的脚本。为了说 “Hello”,你需要创建一个 say 视图,以便显示从操作方法中传来的 message 参数。

<?php
use yii\helpers\Html;
?>
<?= Html::encode($message) ?>

say 视图应该存为 views/site/say.php 文件。当一个操作中调用了 yii\web\Controller::render()|render() 方法时,它将会按 views/控制器 ID/视图名.php 路径加载 PHP 文件。 注意以上代码,message 参数在输出之前被 yii\helpers\Html::encode()|HTML-encoded 方法处理过。这很有必要,当参数来自于最终用户时,参数中可能隐含的恶意 JavaScript 代码会导致跨站脚本(XSS)攻击。 当然了,你大概会在 say 视图里放入更多内容。内容可以由 HTML 标签,纯文本,甚至 PHP 语句组成。实际上 say 视图就是一个由 yii\web\Controller::render()|render() 执行的 PHP 脚本。视图脚本输出的内容将会作为响应结果返回给应用。应用将依次输出结果给最终用户。

尝试下

创建完操作和视图后,你就可以通过下面的 URL 访问新页面了:

http://hostname/index.php?r=site/say&message=Hello+World

[Hello World] 这个 URL 将会输出包含 “Hello World” 的页面,页面和应用里的其它页面使用同样的头部和尾部。 如果你省略 URL 中的 message 参数,将会看到页面只显示 “Hello”。这是因为 message 被作为一个参数传给 actionSay() 方法,当省略它时,参数将使用默认的 “Hello” 代替。 > 补充:新页面和其它页面使用同样的头部和尾部是因为 yii\web\Controller::render()|render() 方法会自动把 say 视图执行的结果嵌入称为[布局]的文件中,本例中是 views/layouts/main.php。 上面 URL 中的参数 r 需要更多解释。它代表[路由],是整个应用级的,指向特定操作的独立 ID。路由格式是 控制器 ID/操作 ID。应用接受请求的时候会检查参数,使用控制器 ID 去确定哪个控制器应该被用来处理请求。然后相应控制器将使用操作 ID 去确定哪个操作方法将被用来做具体工作。上述例子中,路由 site/say 将被解析至 SiteController 控制器和其中的 say 操作。因此 SiteController::actionSay() 方法将被调用处理请求。 > 补充:与操作一样,一个应用中控制器同样有唯一的 ID。控制器 ID 和操作 ID 使用同样的命名规则。控制器的类名源自于控制器 ID,移除了连字符,每个单词首字母大写,并加上 Controller 后缀。例子:控制器 ID post-comment 相当于控制器类名 PostCommentController。

总结

通过本章节你接触了 MVC 设计模式中的控制器和视图部分。创建了一个操作作为控制器的一部分去处理特定请求。然后又创建了一个视图去构造响应内容。在这个小例子中,没有模型调用,唯一涉及到数据的地方是 message 参数。 你同样学习了 Yii 路由的相关内容,它是用户请求与控制器操作之间的桥梁。 下一章,你将学习如何创建一个模型,以及添加一个包含 HTML 表单的页面。

使用表单 - Yii2教程

使用表单

本章节将介绍如何创建一个从用户那搜集数据的表单页。该页将显示一个包含 name 输入框和 email 输入框的表单。当搜集完这两部分信息后,页面将会显示用户输入的信息。

为了实现这个目标,除了创建一个[操作]和两个[视图]外,还需要创建一个[模型]。

贯穿整个小节,你将会学到:

  • 创建一个模型代表用户通过表单输入的数据
  • 声明规则去验证输入的数据
  • 在视图中生成一个 HTML 表单

创建模型

模型类 EntryForm 代表从用户那请求的数据,该类如下所示并存储在 models/EntryForm.php 文件中。请参考[类自动加载]章节获取更多关于类命名约定的介绍。

<?php
namespace app\models;
use yii\base\Model;
class EntryForm extends Model
{
public $name;
public $email;

public function rules()
{
return [
`'name', 'email'], 'required'],
['email', 'email'],
];
}
}

该类继承自 yii\base\Model,Yii 提供的一个基类,通常用来表示数据。

补充:yii\base\Model 被用于普通模型类的父类并与数据表无关。yii\db\ActiveRecord 通常是普通模型类的父类但与数据表有关联(译者注:yii\db\ActiveRecord 类其实也是继承自 yii\base\Model,增加了数据库处理)。

EntryForm 类包含 name 和 email 两个公共成员,用来储存用户输入的数据。它还包含一个名为 rules() 的方法,用来返回数据验证规则的集合。上面声明的验证规则表示:

  • name 和 email 值都是必须的
  • mail 的值必须满足 email 地址验证

如果你有一个从用户那搜集数据的 EntryForm 对象,你可以调用它的 yii\base\Model::validate()|validate() 方法触发数据验证。如果有数据验证失败,将把 yii\base\Model::hasErrors|hasErrors 属性设为 ture,想要知道具体发生什么错误就调用 yii\base\Model::getErrors|getErrors。

<?php
$model = new EntryForm();
$model->name = 'Qiang';
$model->email = 'bad';
if ($model->validate()) {
// 验证成功!
} else {
// 失败!
// 使用 $model->getErrors() 获取错误详情
}

创建操作

下面你得在 site 控制器中创建一个 entry 操作用于新建的模型。操作的创建和使用已经在说一声你好小节中解释了。

<?php
namespace app\controllers;
use Yii;
use yii\web\Controller;
use app\models\EntryForm;
class SiteController extends Controller
{
// ...其它代码...

public function actionEntry()
{
$model = new EntryForm;

if ($model->load(Yii::$app->request->post()) && $model->validate()) {
// 验证 $model 收到的数据

// 做些有意义的事 ...

return $this->render('entry-confirm', ['model' => $model]);
} else {
// 无论是初始化显示还是数据验证错误
return $this->render('entry', ['model' => $model]);
}
}
}

该操作首先创建了一个 EntryForm 对象。然后尝试从 $_POST 搜集用户提交的数据,由 Yii 的 yii\web\Request::post() 方法负责搜集。如果模型被成功填充数据(也就是说用户已经提交了 HTML 表单),操作将调用 yii\base\Model::validate()|validate() 去确保用户提交的是有效数据。

补充:表达式 Yii::$app 代表[应用]实例,它是一个全局可访问的单例。同时它也是一个[服务定位器],能提供 request,response,db 等等特定功能的组件。在上面的代码里就是使用 request 组件来访问应用实例收到的 $_POST 数据。

用户提交表单后,操作将会渲染一个名为 entry-confirm 的视图去确认用户输入的数据。如果没填表单就提交,或数据包含错误(译者:如 email 格式不对),entry 视图将会渲染输出,连同表单一起输出的还有验证错误的详细信息。

注意:在这个简单例子里我们只是呈现了有效数据的确认页面。实践中你应该考虑使用 yii\web\Controller::refresh()|refresh() 或 yii\web\Controller::redirect()|redirect() 去避免表单重复提交问题

创建视图

最后创建两个视图文件 entry-confirm 和 entry。他们会被刚才创建的 entry 操作渲染。

entry-confirm 视图简单地显示提交的 name 和 email 数据。视图文件保存在 views/site/entry-confirm.php。

<?php
use yii\helpers\Html;
?>
<p>You have entered the following information:</p>

<ul>
<li><label>Name</label>: <?= Html::encode($model->name) ?></li>
<li><label>Email</label>: <?= Html::encode($model->email) ?></li>
</ul>

entry 视图显示一个 HTML 表单。视图文件保存在 views/site/entry.php。

<?php
use yii\helpers\Html;
use yii\widgets\ActiveForm;
?>
<?php $form = ActiveForm::begin(); ?>
<?= $form->field($model, 'name') ?>
<?= $form->field($model, 'email') ?>
<div class="form-group">
<?= Html::submitButton('Submit', ['class' => 'btn btn-primary']) ?>
</div>
<?php ActiveForm::end(); ?>

视图使用了一个功能强大的小部件 yii\widgets\ActiveForm|ActiveForm 去生成 HTML 表单。其中的 begin() 和 end() 分别用来渲染表单的开始和关闭标签。在这两个方法之间使用了 yii\widgets\ActiveForm::field()|field() 方法去创建输入框。第一个输入框用于 “name”,第二个输入框用于 “email”。之后使用 yii\helpers\Html::submitButton() 方法生成提交按钮。

尝试下

用浏览器访问下面的 URL 看它能否工作:

http://hostname/index.php?r=site/entry

你会看到一个包含两个输入框的表单的页面。每个输入框的前面都有一个标签指明应该输入的数据类型。如果什么都不填就点击提交按钮,或填入格式不正确的 email 地址,将会看到在对应的输入框下显示错误信息。 [验证错误的表单] 输入有效的 name 和 email 信息并提交后,将会看到一个显示你所提交数据的确认页面。 [输入数据的确认页]

效果说明

你可能会好奇 HTML 表单暗地里是如何工作的呢,看起来它可以为每个输入框显示文字标签,而当你没输入正确的信息时又不需要刷新页面就能给出错误提示,似乎有些神奇。

是的,其实数据首先由客户端 JavaScript 脚本验证,然后才会提交给服务器通过 PHP 验证。yii\widgets\ActiveForm 足够智能到把你在 EntryForm 模型中声明的验证规则转化成客户端 JavaScript 脚本去执行验证。如果用户浏览器禁用了 JavaScript, 服务器端仍然会像 actionEntry() 方法里这样验证一遍数据。这保证了任何情况下用户提交的数据都是有效的。

警告:客户端验证是提高用户体验的手段。无论它是否正常启用,服务端验证则都是必须的,请不要忽略它。

输入框的文字标签是 field() 方法生成的,内容就是模型中该数据的属性名。例如模型中的 name 属性生成的标签就是 Name。

你可以在视图中自定义标签:

<?= $form->field($model, 'name')->label('自定义 Name') ?>
<?= $form->field($model, 'email')->label('自定义 Email') ?>

补充:Yii 提供了相当多类似的小部件去帮你生成复杂且动态的视图。在后面你还会了解到自己写小部件是多么简单。你可能会把自己的很多视图代码转化成小部件以提高重用,加快开发效率。

总结

本章节指南中你接触了 MVC 设计模式的每个部分。学到了如何创建一个模型代表用户数据并验证它的有效性。

你还学到了如何从用户那获取数据并在浏览器上回显给用户。这本来是开发应用的过程中比较耗时的任务,好在 Yii 提供了强大的小部件让它变得如此简单。

下一章你将学习如何使用数据库,几乎每个应用都需要数据库。

Yii2使用数据库 - Yii2教程

使用数据库

本章节将介绍如何如何创建一个从数据表 country 中获取国家数据并显示出来的页面。为了实现这个目标,你将会配置一个数据库连接,创建一个活动记录类,并且创建一个操作及一个视图。

贯穿整个章节,你将会学到:

  • 配置一个数据库连接
  • 定义一个活动记录类
  • 使用活动记录从数据库中查询数据
  • 以分页方式在视图中显示数据

请注意,为了掌握本章你应该具备最基本的数据库知识和使用经验。尤其是应该知道如何创建数据库,如何通过数据库终端执行 SQL 语句。

准备数据库

首先创建一个名为 yii2basic 的数据库,应用将从这个数据库中获取数据。你可以创建 SQLite,MySQL,PostregSQL,MSSQL 或 Oracle 数据库,Yii 内置多种数据库支持。简单起见后面的内容将以 MySQL 为例做演示。

然后在数据库中创建一个名为 country 的表并插入简单的数据。可以执行下面的语句:

CREATE TABLE `country` (
`code` CHAR(2) NOT NULL PRIMARY KEY,
`name` CHAR(52) NOT NULL,
`population` INT(11) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `Country` VALUES ('AU','Australia',18886000);
INSERT INTO `Country` VALUES ('BR','Brazil',170115000);
INSERT INTO `Country` VALUES ('CA','Canada',1147000);
INSERT INTO `Country` VALUES ('CN','China',1277558000);
INSERT INTO `Country` VALUES ('DE','Germany',82164700);
INSERT INTO `Country` VALUES ('FR','France',59225700);
INSERT INTO `Country` VALUES ('GB','United Kingdom',59623400);
INSERT INTO `Country` VALUES ('IN','India',1013662000);
INSERT INTO `Country` VALUES ('RU','Russia',146934000);
INSERT INTO `Country` VALUES ('US','United States',278357000);

于是便有了一个名为 yii2basic 的数据库,在这个数据库中有一个包含三个字段的数据表 country,表中有十行数据。

配置数据库连接

开始之前,请确保你已经安装了 PHP PDO 扩展和你所使用的数据库的 PDO 驱动(例如 MySQL 的 pdo_mysql)。对于使用关系型数据库来讲,这是基本要求。

驱动和扩展安装可用后,打开 config/db.php 修改里面的配置参数对应你的数据库配置。该文件默认包含这些内容:

<?php

return [
'class' => 'yiidbConnection',
'dsn' => 'mysql:host=localhost;dbname=yii2basic',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
];

config/db/php 是一个典型的基于文件的配置工具。这个文件配置了数据库连接 yiidbConnection 的创建和初始化参数,应用的 SQL 查询正是基于这个数据库。

上面配置的数据库连接可以在应用中通过 Yii::$app-&gt;db 访问。

补充:config/db.php 将被包含在应用配置文件 config/web.php 中,后者指定了整个应用如何初始化。请参考配置章节了解更多信息。

创建活动记录

创建一个继承自活动记录类的类 Country,把它放在 models/Country.php,去表示和获取 country 表的数据。

<?php

namespace appmodels;

use yiidbActiveRecord;

class Country extends ActiveRecord
{
}

这个 Country 类继承自 yiidbActiveRecord。你不用在里面写任何代码。只需要像现在这样,Yii 就能根据类名去猜测对应的数据表名。

补充:如果类名和数据表名不能直接对应,可以重写 yiidbActiveRecord::tableName()|tableName() 方法去显式指定相关表名。

使用 Country 类可以很容易地操作 country 表数据,就像这段代码:

use appmodelsCountry;

// 获取 country 表的所有行并以 name 排序
$countries = Country::find()->orderBy('name')->all();

// 获取主键为 “US” 的行
$country = Country::findOne('US');

// 输出 “United States”
echo $country->name;

// 修改 name 为 “U.S.A.” 并在数据库中保存更改
$country->name = 'U.S.A.';
$country->save();

补充:活动记录是面向对象、功能强大的访问和操作数据库数据的方式。你可以在活动记录章节了解更多信息。除此之外你还可以使用另一种更原生的称做数据访问对象的方法操作数据库数据。

创建操作

为了向最终用户显示国家数据,你需要创建一个操作。相比之前小节掌握的在 site 控制器中创建操作,在这里为所有和国家有关的数据新建一个控制器更加合理。新控制器名为CountryController,并在其中创建一个 index 操作,如下:

<?php

namespace appcontrollers;

use yiiwebController;
use yiidataPagination;
use appmodelsCountry;

class CountryController extends Controller
{
public function actionIndex()
{
$query = Country::find();

$pagination = new Pagination([
'defaultPageSize' => 5,
'totalCount' => $query->count(),
]);

$countries = $query->orderBy('name')
->offset($pagination->offset)
->limit($pagination->limit)
->all();

return $this->render('index', [
'countries' => $countries,
'pagination' => $pagination,
]);
}
}

把上面的代码保存在 controllers/CountryController.php。

index 操作调用了活动记录 Country::find() 方法,去生成查询语句并从 country 表中取回所有数据。为了限定每个请求所返回的国家数量,查询在 yiidataPagination 对象的帮助下进行分页。 Pagination 对象的使命主要有两点:

  • 为 SQL 查询语句设置 offset 和 limit 从句,确保每个请求只需返回一页数据(本例中每页是 5 行)。
  • 在视图中显示一个由页码列表组成的分页器,这点将在后面的段落中解释。

在代码末尾,index 操作渲染一个名为 index 的视图,并传递国家数据和分页信息进去。

创建视图

在 views 目录下先创建一个名为 country 的子目录。这个目录存储所有由 country 控制器渲染的视图。在 views/country 目录下创建一个名为 index.php 的视图文件,内容如下:

<?php
use yiihelpersHtml;
use yiiwidgetsLinkPager;
?>
<h1>Countries</h1>
<ul>
<?php foreach ($countries as $country): ?>
<li>
<?= Html::encode("{$country->name} ({$country->code})") ?>:
<?= $country->population ?>
</li>
<?php endforeach; ?>
</ul>

<?= LinkPager::widget(['pagination' => $pagination]) ?>

这个视图包含两部分用以显示国家数据。第一部分遍历国家数据并以无序 HTML 列表渲染出来。第二部分使用 yiiwidgetsLinkPager 去渲染从操作中传来的分页信息。小部件LinkPager 显示一个分页按钮的列表。点击任何一个按钮都会跳转到对应的分页。

尝试下

浏览器访问下面的 URL 看看能否工作:

http://hostname/index.php?r=country/index

首先你会看到显示着五个国家的列表页面。在国家下面,你还会看到一个包含四个按钮的分页器。如果你点击按钮 “2”,将会跳转到显示另外五个国家的页面,也就是第二页记录。如果观察仔细点你还会看到浏览器的 URL 变成了:

http://hostname/index.php?r=country/index&page=2

在这个场景里,yiidataPagination|Pagination 提供了为数据结果集分页的所有功能:

  • 首先 yiidataPagination|Pagination 把 SELECT 的子查询 LIMIT 5 OFFSET 0 数据表示成第一页。因此开头的五条数据会被取出并显示。
  • 然后小部件 yiiwidgetsLinkPager|LinkPager 使用 yiidataPagination::createUrl()|Pagination::createUrl() 方法生成的 URL 去渲染翻页按钮。URL 中包含必要的参数 page 才能查询不同的页面编号。
  • 如果你点击按钮 “2”,将会发起一个路由为 country/index 的新请求。yiidataPagination|Pagination 接收到 URL 中的 page 参数把当前的页码设为 2。新的数据库请求将会以 LIMIT 5 OFFSET 5 查询并显示。

总结

本章节中你学到了如何使用数据库。你还学到了如何取出并使用 yiidataPagination 和 yiiwidgetsLinkPager 显示数据。

下一章中你会学到如何使用 Yii 中强大的代码生成器 Gii,去帮助你实现一些常用的功能需求,例如增查改删(CRUD)数据表中的数据。事实上你之前所写的代码全部都可以由 Gii 自动生成。

使用Gii生成代码 - Yii2教程

使用 Gii 生成代码

本章节将介绍如何使用 Gii 去自动生成 Web 站点常用功能的代码。使用 Gii 生成代码非常简单,只要按照 Gii 页面上的介绍输入正确的信息即可。

贯穿本章节,你将会学到:

  • 在你的应用中开启 Gii
  • 使用 Gii 去生成活动记录类
  • 使用 Gii 去生成数据表操作的增查改删(CRUD)代码
  • 自定义 Gii 生成的代码

开始 Gii

Gii 是 Yii 中的一个模块。可以通过配置应用的 yiiaseApplication::modules|modules 属性开启它。通常来讲在 config/web.php 文件中会有以下配置代码:

$config = [ ... ];

if (YII_ENV_DEV) {
$config['bootstrap'][] = 'gii';
$config['modules']['gii'] = 'yiigiiModule';
}

这段配置的意思是如果当前是开发环境,应用会包含 gii 模块,模块类是 yiigiiModule。

如果你检查应用的入口脚本 web/index.php,将看到这行代码将 YII_ENV_DEV 设为 true:

defined('YII_ENV') or define('YII_ENV', 'dev');

代码设置应用处于开发模式下,按照上面的配置会打开 Gii 模块。你可以直接通过 URL 访问 Gii:

http://hostname/index.php?r=gii

生成活动记录类

选择 “Model Generator” (点击 Gii 首页的链接)去生成活动记录类。并像这样填写表单:

  • Table Name: country
  • Model Class: Country

然后点击 “Preview” 按钮。你会看到 models/Country.php 被列在将要生成的文件列表中。可以点击文件名预览内容。

如果你已经创建过同样的文件,使用 Gii 可以覆写它,点击文件名旁边的 diff 能查看现有文件与将要生成的文件的内容区别。

想要覆写已存在文件,选中 “overwrite” 下的复选框然后点击 “Generator”。如果是新文件,只点击 “Generator” 就好。

接下来你会看到一个包含已生成文件的说明页面。如果生成过程中覆写过文件,还会有一条信息说明代码是重新生成覆盖的。

生成 CRUD 代码

CRUD 代表增,查,改,删操作,这是绝大多数 Web 站点常用的数据处理方式。选择 Gii 中的 “CRUD Generator” (点击 Gii 首页的链接)去创建 CRUD 功能。之前的 “country” 例子需要像这样填写表单:

  • Model Class: appmodelsCountry
  • Search Model Class: appmodelsCountrySearch
  • Controller Class: appcontrollersCountryController

然后点击 “Preview” 按钮。你会看到下述将要生成的文件列表。

NEED THE IMAGE HERE / 等待官方补充图片

如果你之前创建过 controllers/CountryController.php 和 views/country/index.php 文件(在指南的使用数据库小节),选中 “overwrite” 下的复选框覆写它们(之前的文件没能全部支持 CRUD)。

尝试下

用浏览器访问下面的 URL 查看生成代码的运行:

http://hostname/index.php?r=country/index

可以看到一个栅格显示着从数据表中获取的国家数据。支持在列头对数据进行排序,输入筛选条件进行筛选。

可以浏览详情,编辑,或删除栅格中的每个国家。还可以点击栅格上方的 “Create Country” 按钮通过表单创建新国家。

下面列出由 Gii 生成的文件,以便你研习功能和实现,或修改它们。

  • 控制器:controllers/CountryController.php
  • 模型:models/Country.php 和 models/CountrySearch.php
  • 视图:views/country/*.php

补充:Gii 被设计成高度可定制和可扩展的代码生成工具。使用它可以大幅提高应用开发速度。请参考 Gii 小节了解更多内容。

总结

本章学习了如何使用 Gii 去生成为数据表中数据实现完整 CRUD 功能的代码。

组件(Component) - Yii2教程

组件(Component)

组件是 Yii 应用的主要基石。是 yiiaseComponent 类或其子类的实例。三个用以区分它和其它类的主要功能有:

  • 属性(Property)
  • 事件(Event)
  • 行为(Behavior)

或单独使用,或彼此配合,这些功能的应用让 Yii 的类变得更加灵活和易用。以小部件 yiijuiDatePicker|日期选择器 来举例,这是个方便你在 视图 中生成一个交互式日期选择器的 UI 组件:

1. use yiijuiDatePicker;

3. echo DatePicker::widget([
4. 'language' =&gt; 'zh-CN',
5. 'name' =&gt; 'country',
6. 'clientOptions' =&gt; [
7. 'dateFormat' =&gt; 'yy-mm-dd',
8. ],
9. ]);

这个小部件继承自 yiiaseComponent,它的各项属性改写起来会很容易。

正是因为组件功能的强大,他们比常规的对象(Object)稍微重量级一点,因为他们要使用额外的内存和 CPU 时间来处理 事件行为 。如果你不需要这两项功能,可以继承 yiiaseObject 而不是 yiiaseComponent。这样组件可以像普通 PHP 对象一样高效,同时还支持属性(Property)功能。

当继承 yiiaseComponent 或 yiiaseObject 时,推荐你使用如下的编码风格:

  • 若你需要重写构造方法(Constructor),传入 $config 作为构造器方法最后一个参数,然后把它传递给父类的构造方法。
  • 永远在你重写的构造方法结尾处调用一下父类的构造方法。
  • 如果你重写了 yiiaseObject::init() 方法,请确保你在 init 方法的开头处调用了父类的 init 方法。

例子如下:

1. namespace yiicomponentsMyClass;

3. use yiiaseObject;

5. class MyClass extends Object
6. {
7. public $prop1;
8. public $prop2;

10. public function __construct($param1, $param2, $config = [])
11. {
12. // ... 配置生效前的初始化过程

14. parent::__construct($config);
15. }

17. public function init()
18. {
19. parent::init();

21. // ... 配置生效后的初始化过程
22. }
23. }

另外,为了让组件可以在创建实例时能被正确配置,请遵照以下操作流程:

1. $component = new MyClass(1, 2, ['prop1' =&gt; 3, 'prop2' =&gt; 4]);
2. // 方法二:
3. $component = Yii::createObject([
4. 'class' =&gt; MyClass::className(),
5. 'prop1' =&gt; 3,
6. 'prop2' =&gt; 4,
7. ], [1, 2]);

补充:尽管调用 Yii::createObject() 的方法看起来更加复杂,但这主要因为它更加灵活强大,它是基于依赖注入容器实现的。

yiiaseObject 类执行时的生命周期如下:

  1. 构造方法内的预初始化过程。你可以在这儿给各属性设置缺省值。
  2. 通过 $config 配置对象。配置的过程可能会覆盖掉先前在构造方法内设置的默认值。
  3. 在 yiiaseObject::init()|init() 方法内进行初始化后的收尾工作。你可以通过重写此方法,进行一些良品检验,属性的初始化之类的工作。
  4. 对象方法调用。

前三步都是在对象的构造方法内发生的。这意味着一旦你获得了一个对象实例,那么它就已经初始化就绪可供使用。

属性(Property) - Yii2教程

属性(Property)

在 PHP 中,类的成员变量也被称为属性(properties)。它们是类定义的一部分,用来表现一个实例的状态(也就是区分类的不同实例)。在具体实践中,常常会想用一个稍微特殊些的方法实现属性的读写。例如,要对label 属性执行 trim 操作,可以用以下代码实现:

1. $object-&gt;label = trim($label);

上述代码的缺点是只要修改 label 属性就必须再次调用 trim() 函数。若将来需要用其它方式处理 label 属性,比如首字母大写,就不得不修改所有给 label 属性赋值的代码。这种代码的重复会导致 bug,这种实践显然需要尽可能避免。

为解决该问题,Yii 引入了一个名为 yii\base\Object 的基类,它支持基于类内的 getter 和 setter(读取器和设定器)方法来定义属性。如果某类需要支持这个特性,只需要继承 yii\base\Object 或其子类即可。

补充:几乎每个 Yii 框架的核心类都继承自 yii\base\Object 或其子类。这意味着只要在核心类中见到 getter 或 setter 方法,就可以像调用属性一样调用它。

getter 方法是名称以 get 开头的方法,而 setter 方法名以 set 开头。方法名中 get 或 set 后面的部分就定义了该属性的名字。如下面代码所示,getter 方法 getLabel() 和 setter 方法 setLabel() 操作的是 label属性,:

1. namespace app\\components;

3. use yii\\base\\Object;

5. class Foo extend Object
6. {
7. private $_label;

9. public function getLabel()
10. {
11. return $this-&gt;_label;
12. }

14. public function setLabel($value)
15. {
16. $this-&gt;_label = trim($value);
17. }
18. }

(详细解释:getter 和 setter 方法创建了一个名为 label 的属性,在这个例子里,它指向一个私有的内部属性_label。)

getter/setter 定义的属性用法与类成员变量一样。两者主要的区别是:当这种属性被读取时,对应的 getter 方法将被调用;而当属性被赋值时,对应的 setter 方法就调用。如:

1. // 等效于 $label = $object-&gt;getLabel();
2. $label = $object-&gt;label;

4. // 等效于 $object-&gt;setLabel('abc');
5. $object-&gt;label = 'abc';

只定义了 getter 没有 setter 的属性是只读属性。尝试赋值给这样的属性将导致 yii\base\InvalidCallException|InvalidCallException (无效调用)异常。类似的,只有 setter 方法而没有 getter 方法定义的属性是只写属性,尝试读取这种属性也会触发异常。使用只写属性的情况几乎没有。

通过 getter 和 setter 定义的属性也有一些特殊规则和限制:

  • 这类属性的名字是不区分大小写的。如,$object->label 和 $object->Label 是同一个属性。因为 PHP 方法名是不区分大小写的。
  • 如果此类属性名和类成员变量相同,以后者为准。例如,假设以上 Foo 类有个 label 成员变量,然后给$object->label = 'abc' 赋值,将赋给成员变量而不是 setter setLabel() 方法。
  • 这类属性不支持可见性(访问限制)。定义属性的 getter 和 setter 方法是 public、protected 还是 private 对属性的可见性没有任何影响。
  • 这类属性的 getter 和 setter 方法只能定义为非静态的,若定义为静态方法(static)则不会以相同方式处理。

回到开头提到的问题,与其处处要调用 trim() 函数,现在我们只需在 setter setLabel() 方法内调用一次。如果 label 首字母变成大写的新要求来了,我们只需要修改setLabel() 方法,而无须接触任何其它代码。

事件 - Yii2教程

事件

事件可以将自定义代码“注入”到现有代码中的特定执行点。附加自定义代码到某个事件,当这个事件被触发时,这些代码就会自动执行。例如,邮件程序对象成功发出消息时可触发 messageSent 事件。如想追踪成功发送的消息,可以附加相应追踪代码到 messageSent 事件。

Yii 引入了名为 yii\base\Component 的基类以支持事件。如果一个类需要触发事件就应该继承 yii\base\Component 或其子类。

事件处理器(Event Handlers)

事件处理器是一个PHP 回调函数,当它所附加到的事件被触发时它就会执行。可以使用以下回调函数之一:

  • 字符串形式指定的 PHP 全局函数,如 'trim' ;
  • 对象名和方法名数组形式指定的对象方法,如 [$object, $method] ;
  • 类名和方法名数组形式指定的静态类方法,如 [$class, $method] ;
  • 匿名函数,如 function ($event) { ... } 。

事件处理器的格式是:

1. function ($event) {
2. // $event 是 yii\\base\\Event 或其子类的对象
3. }

通过 $event 参数,事件处理器就获得了以下有关事件的信息:

  • yii\base\Event::name|event name:事件名
  • yii\base\Event::sender|event sender:调用 trigger() 方法的对象
  • yii\base\Event::data|custom data:附加事件处理器时传入的数据,默认为空,后文详述

附加事件处理器

调用 yii\base\Component::on() 方法来附加处理器到事件上。如:

1. $foo = new Foo;

3. // 处理器是全局函数
4. $foo-&gt;on(Foo::EVENT_HELLO, 'function_name');

6. // 处理器是对象方法
7. $foo-&gt;on(Foo::EVENT_HELLO, [$object, 'methodName']);

9. // 处理器是静态类方法
10. $foo-&gt;on(Foo::EVENT_HELLO, ['app\\components\\Bar', 'methodName']);

12. // 处理器是匿名函数
13. $foo-&gt;on(Foo::EVENT_HELLO, function ($event) {
14. //事件处理逻辑
15. });

附加事件处理器时可以提供额外数据作为 yii\base\Component::on() 方法的第三个参数。数据在事件被触发和处理器被调用时能被处理器使用。如:

1. // 当事件被触发时以下代码显示 "abc"
2. // 因为 $event-&gt;data 包括被传递到 "on" 方法的数据
3. $foo-&gt;on(Foo::EVENT_HELLO, function ($event) {
4. echo $event-&gt;data;
5. }, 'abc');

时间处理器顺序

可以附加一个或多个处理器到一个事件。当事件被触发,已附加的处理器将按附加次序依次调用。如果某个处理器需要停止其后的处理器调用,可以设置 $event 参数的 yii\base\Event::handled 属性为真,如下:

1. $foo-&gt;on(Foo::EVENT_HELLO, function ($event) {
2. $event-&gt;handled = true;
3. });

默认新附加的事件处理器排在已存在处理器队列的最后。因此,这个处理器将在事件被触发时最后一个调用。在处理器队列最前面插入新处理器将使该处理器最先调用,可以传递第四个参数 $append 为假并调用 yii\base\Component::on() 方法实现:

``php $foo->on(Foo::EVENT_HELLO, function ($event) { // 这个处理器将被插入到处理器队列的第一位... }, $data, false);

触发事件
----------

事件通过调用 `yii\base\Component::trigger()` 方法触发,此方法须传递**事件名**,还可以传递一个事件对象,用来传递参数到事件处理器。如:

```php
namespace app\components;

use yii\base\Component;
use yii\base\Event;

class Foo extends Component
{
const EVENT_HELLO = 'hello';

public function bar()
{
$this->trigger(self::EVENT_HELLO);
}
}

以上代码当调用 bar() ,它将触发名为 hello 的事件。

提示:推荐使用类常量来表示事件名。上例中,常量 EVENT_HELLO 用来表示 hello 。这有两个好处。第一,它可以防止拼写错误并支持 IDE 的自动完成。第二,只要简单检查常量声明就能了解一个类支持哪些事件。

有时想要在触发事件时同时传递一些额外信息到事件处理器。例如,邮件程序要传递消息信息到 messageSent事件的处理器以便处理器了解哪些消息被发送了。为此,可以提供一个事件对象作为 yii\base\Component::trigger() 方法的第二个参数。这个事件对象必须是 yii\base\Event 类或其子类的实例。如:

1. namespace app\\components;

3. use yii\\base\\Component;
4. use yii\\base\\Event;

6. class MessageEvent extends Event
7. {
8. public $message;
9. }

11. class Mailer extends Component
12. {
13. const EVENT_MESSAGE_SENT = 'messageSent';

15. public function send($message)
16. {
17. // ...发送 $message 的逻辑...

19. $event = new MessageEvent;
20. $event-&gt;message = $message;
21. $this-&gt;trigger(self::EVENT_MESSAGE_SENT, $event);
22. }
23. }

当 yii\base\Component::trigger() 方法被调用时,它将调用所有附加到命名事件(trigger 方法第一个参数)的事件处理器。

移除事件处理器

从事件移除处理器,调用 yii\base\Component::off() 方法。如:

1. // 处理器是全局函数
2. $foo-&gt;off(Foo::EVENT_HELLO, 'function_name');

4. // 处理器是对象方法
5. $foo-&gt;off(Foo::EVENT_HELLO, [$object, 'methodName']);

7. // 处理器是静态类方法
8. $foo-&gt;off(Foo::EVENT_HELLO, ['app\\components\\Bar', 'methodName']);

10. // 处理器是匿名函数
11. $foo-&gt;off(Foo::EVENT_HELLO, $anonymousFunction);

注意当匿名函数附加到事件后一般不要尝试移除匿名函数,除非你在某处存储了它。以上示例中,假设匿名函数存储为变量 $anonymousFunction 。

移除事件的全部处理器,简单调用 yii\base\Component::off() 即可,不需要第二个参数:

1. $foo-&gt;off(Foo::EVENT_HELLO);

类级别的事件处理器

以上部分,我们叙述了在实例级别如何附加处理器到事件。有时想要一个类的所有实例而不是一个指定的实例都响应一个被触发的事件,并不是一个个附加事件处理器到每个实例,而是通过调用静态方法 yii\base\Event::on() 在类级别附加处理器。

例如,活动记录对象要在每次往数据库新增一条新记录时触发一个 yii\base\ActiveRecord::EVENT_AFTER_INSERT 事件。要追踪每个活动记录对象的新增记录完成情况,应如下写代码:

1. use Yii;
2. use yii\\base\\Event;
3. use yii\\db\\ActiveRecord;

5. Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) {
6. Yii::trace(get_class($event-&gt;sender) . ' is inserted');
7. });

每当 yii\base\ActiveRecord|ActiveRecord 或其子类的实例触发 yii\base\ActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT 事件时,这个事件处理器都会执行。在这个处理器中,可以通过 $event-&gt;sender 获取触发事件的对象。

当对象触发事件时,它首先调用实例级别的处理器,然后才会调用类级别处理器。

可调用静态方法yii\base\Event::trigger()来触发一个类级别事件。类级别事件不与特定对象相关联。因此,它只会引起类级别事件处理器的调用。如:

1. use yii\\base\\Event;

3. Event::on(Foo::className(), Foo::EVENT_HELLO, function ($event) {
4. echo $event-&gt;sender; // 显示 "app\\models\\Foo"
5. });

7. Event::trigger(Foo::className(), Foo::EVENT_HELLO);

注意这种情况下 $event-&gt;sender 指向触发事件的类名而不是对象实例。

注意:因为类级别的处理器响应类和其子类的所有实例触发的事件,必须谨慎使用,尤其是底层的基类,如 yii\base\Object。

移除类级别的事件处理器只需调用yii\base\Event::off(),如:

1. // 移除 $handler
2. Event::off(Foo::className(), Foo::EVENT_HELLO, $handler);

4. // 移除 Foo::EVENT_HELLO 事件的全部处理器
5. Event::off(Foo::className(), Foo::EVENT_HELLO);

全局事件

所谓全局事件实际上是一个基于以上叙述的事件机制的戏法。它需要一个全局可访问的单例,如应用实例。

事件触发者不调用其自身的 trigger() 方法,而是调用单例的 trigger() 方法来触发全局事件。类似地,事件处理器被附加到单例的事件。如:

1. use Yii;
2. use yii\\base\\Event;
3. use app\\components\\Foo;

5. Yii::$app-&gt;on('bar', function ($event) {
6. echo get_class($event-&gt;sender); // 显示 "app\\components\\Foo"
7. });

9. Yii::$app-&gt;trigger('bar', new Event(['sender' =&gt; new Foo]));

全局事件的一个好处是当附加处理器到一个对象要触发的事件时,不需要产生该对象。相反,处理器附加和事件触发都通过单例(如应用实例)完成。

然而,因为全局事件的命名空间由各方共享,应合理命名全局事件,如引入一些命名空间(例:"frontend.mail.sent", "backend.mail.sent")。

行为 - Yii2教程

行为

行为是 yii\base\Behavior 或其子类的实例。行为,也称为 mixins,可以无须改变类继承关系即可增强一个已有的 yii\base\Component|组件 类功能。当行为附加到组件后,它将“注入”它的方法和属性到组件,然后可以像访问组件内定义的方法和属性一样访问它们。此外,行为通过组件能响应被触发的事件,从而自定义或调整组件正常执行的代码。

定义行为

要定义行为,通过继承 yii\base\Behavior 或其子类来建立一个类。如:

1. namespace app\\components;

3. use yii\\base\\Model;
4. use yii\\base\\Behavior;

6. class MyBehavior extends Behavior
7. {
8. public $prop1;

10. private $_prop2;

12. public function getProp2()
13. {
14. return $this-&gt;_prop2;
15. }

17. public function setProp2($value)
18. {
19. $this-&gt;_prop2 = $value;
20. }

22. public function foo()
23. {
24. // ...
25. }
26. }

以上代码定义了行为类 app\components\MyBehavior 并为要附加行为的组件提供了两个属性 prop1 、prop2 和一个方法 foo() 。注意属性 prop2 是通过 getter getProp2() 和 setter setProp2() 定义的。能这样用是因为 yii\base\Object 是 yii\base\Behavior 的祖先类,此祖先类支持用 getter 和 setter 方法定义属性

提示:在行为内部可以通过 yii\base\Behavior::owner 属性访问行为已附加的组件。

处理事件

如果要让行为响应对应组件的事件触发,就应覆写 yii\base\Behavior::events() 方法,如:

1. namespace app\\components;

3. use yii\\db\\ActiveRecord;
4. use yii\\base\\Behavior;

6. class MyBehavior extends Behavior
7. {
8. // 其它代码

10. public function events()
11. {
12. return [
13. ActiveRecord::EVENT_BEFORE_VALIDATE =&gt; 'beforeValidate',
14. ];
15. }

17. public function beforeValidate($event)
18. {
19. // 处理器方法逻辑
20. }
21. }

yii\base\Behavior::events()|events() 方法返回事件列表和相应的处理器。上例声明了 yii\db\ActiveRecord::EVENT_BEFORE_VALIDATE|EVENT_BEFORE_VALIDATE 事件和它的处理器beforeValidate() 。当指定一个事件处理器时,要使用以下格式之一:

  • 指向行为类的方法名的字符串,如上例所示;
  • 对象或类名和方法名的数组,如 [$object, 'methodName'];
  • 匿名方法。

处理器的格式如下,其中 $event 指向事件参数。关于事件的更多细节请参考事件

1. function ($event) {
2. }

附加行为

可以静态或动态地附加行为到yii\base\Component|组件。前者在实践中更常见。

要静态附加行为,覆写行为要附加的组件类的 yii\base\Component::behaviors()|behaviors() 方法即可。yii\base\Component::behaviors()|behaviors() 方法应该返回行为配置列表。每个行为配置可以是行为类名也可以是配置数组。如:

1. namespace app\\models;

3. use yii\\db\\ActiveRecord;
4. use app\\components\\MyBehavior;

6. class User extends ActiveRecord
7. {
8. public function behaviors()
9. {
10. return [
11. // 匿名行为,只有行为类名
12. MyBehavior::className(),

14. // 命名行为,只有行为类名
15. 'myBehavior2' =&gt; MyBehavior::className(),

17. // 匿名行为,配置数组
18. [
19. 'class' =&gt; MyBehavior::className(),
20. 'prop1' =&gt; 'value1',
21. 'prop2' =&gt; 'value2',
22. ],

24. // 命名行为,配置数组
25. 'myBehavior4' =&gt; [
26. 'class' =&gt; MyBehavior::className(),
27. 'prop1' =&gt; 'value1',
28. 'prop2' =&gt; 'value2',
29. ]
30. ];
31. }
32. }

通过指定行为配置数组相应的键可以给行为关联一个名称。这种行为称为命名行为。上例中,有两个命名行为:myBehavior2 和 myBehavior4 。如果行为没有指定名称就是匿名行为。

要动态附加行为,在对应组件里调用 yii\base\Component::attachBehavior() 方法即可,如:

1. use app\\components\\MyBehavior;

3. // 附加行为对象
4. $component-&gt;attachBehavior('myBehavior1', new MyBehavior);

6. // 附加行为类
7. $component-&gt;attachBehavior('myBehavior2', MyBehavior::className());

9. // 附加配置数组
10. $component-&gt;attachBehavior('myBehavior3', [
11. 'class' =&gt; MyBehavior::className(),
12. 'prop1' =&gt; 'value1',
13. 'prop2' =&gt; 'value2',
14. ]);

可以通过 yii\base\Component::attachBehaviors() 方法一次附加多个行为:

1. $component-&gt;attachBehaviors([
2. 'myBehavior1' =&gt; new MyBehavior, // 命名行为
3. MyBehavior::className(), // 匿名行为
4. ]);

还可以通过配置去附加行为:

1. [
2. 'as myBehavior2' =&gt; MyBehavior::className(),

4. 'as myBehavior3' =&gt; [
5. 'class' =&gt; MyBehavior::className(),
6. 'prop1' =&gt; 'value1',
7. 'prop2' =&gt; 'value2',
8. ],
9. ]

详情请参考配置章节。

使用行为

使用行为,必须像前文描述的一样先把它附加到 yii\base\Component|component 类或其子类。一旦行为附加到组件,就可以直接使用它。

行为附加到组件后,可以通过组件访问一个行为的公共成员变量或 getter 和 setter 方法定义的属性

1. // "prop1" 是定义在行为类的属性
2. echo $component-&gt;prop1;
3. $component-&gt;prop1 = $value;

类似地也可以调用行为的*公共方法:

1. // bar() 是定义在行为类的公共方法
2. $component-&gt;bar();

如你所见,尽管 $component 未定义 prop1 和 bar() ,它们用起来也像组件自己定义的一样。

如果两个行为都定义了一样的属性或方法,并且它们都附加到同一个组件,那么首先附加上的行为在属性或方法被访问时有优先权。

附加行为到组件时的命名行为,可以使用这个名称来访问行为对象,如下所示:

1. $behavior = $component-&gt;getBehavior('myBehavior');

也能获取附加到这个组件的所有行为:

1. $behaviors = $component-&gt;getBehaviors();

移除行为

要移除行为,可以调用 yii\base\Component::detachBehavior() 方法用行为相关联的名字实现:

1. $component-&gt;detachBehavior('myBehavior1');

也可以移除全部行为:

1. $component-&gt;detachBehaviors();

使用 TimestampBehavior

最后以 yii\behaviors\TimestampBehavior 的讲解来结尾,这个行为支持在 yii\db\ActiveRecord|Active Record 存储时自动更新它的时间戳属性。

首先,附加这个行为到计划使用该行为的 yii\db\ActiveRecord|Active Record 类:

1. namespace app\\models\\User;

3. use yii\\db\\ActiveRecord;
4. use yii\\behaviors\\TimestampBehavior;

6. class User extends ActiveRecord
7. {
8. // ...

10. public function behaviors()
11. {
12. return [
13. [
14. 'class' =&gt; TimestampBehavior::className(),
15. 'attributes' =&gt; [
16. ActiveRecord::EVENT_BEFORE_INSERT =&gt; ['created_at', 'updated_at'],
17. ActiveRecord::EVENT_BEFORE_UPDATE =&gt; ['updated_at'],
18. ],
19. ],
20. ];
21. }
22. }

以上指定的行为数组:

  • 当记录插入时,行为将当前时间戳赋值给 created_at 和 updated_at 属性;
  • 当记录更新时,行为将当前时间戳赋值给 updated_at 属性。

保存 User 对象,将会发现它的 created_at 和 updated_at 属性自动填充了当前时间戳:

$user
=
new
User;
$user
-&gt;email =
'test@example.com'
;
$user
-&gt;save();
echo

$user
-&gt;created_at;
// 显示当前时间戳

yii\behaviors\TimestampBehavior|TimestampBehavior 行为还提供了一个有用的方法 yii\behaviors\TimestampBehavior::touch()|touch(),这个方法能将当前时间戳赋值给指定属性并保存到数据库:

$user
->touch(
'login_time'
);

与 PHP traits 的比较

尽管行为在 "注入" 属性和方法到主类方面类似于 traits ,它们在很多方面却不相同。如上所述,它们各有利弊。它们更像是互补的而不是相互替代。

行为的优势

行为类像普通类支持继承。另一方面,traits 可以视为 PHP 语言支持的复制粘贴功能,它不支持继承。

行为无须修改组件类就可动态附加到组件或移除。要使用 traits,必须修改使用它的类。

行为是可配置的而 traits 不能。

行为以响应事件来自定义组件的代码执行。

当不同行为附加到同一组件产生命名冲突时,这个冲突通过先附加行为的优先权自动解决。而由不同 traits 引发的命名冲突需要通过手工重命名冲突属性或方法来解决。

traits 的优势

traits 比起行为更高效,因为行为是对象,消耗时间和内存。

IDE 对 traits 更友好,因为它们是语言结构。

配置 - Yii2教程

配置

在 Yii 中,创建新对象和初始化已存在对象时广泛使用配置。配置通常包含被创建对象的类名和一组将要赋值给对象属性的初始值。还可能包含一组将被附加到对象事件上的句柄。和一组将被附加到对象上的行为

以下代码中的配置被用来创建并初始化一个数据库连接:

1. $config = [
2. 'class' =&gt; 'yii\\db\\Connection',
3. 'dsn' =&gt; 'mysql:host=127.0.0.1;dbname=demo',
4. 'username' =&gt; 'root',
5. 'password' =&gt; '',
6. 'charset' =&gt; 'utf8',
7. ];

9. $db = Yii::createObject($config);

Yii::createObject() 方法接受一个配置并根据配置中指定的类名创建对象。对象实例化后,剩余的参数被用来初始化对象的属性,事件处理和行为。

对于已存在的对象,可以使用 Yii::configure() 方法根据配置去初始化其属性,就像这样:

1. Yii::configure($object, $config);

请注意,如果配置一个已存在的对象,那么配置数组中不应该包含指定类名的 class 元素。

配置的格式

一个配置的格式可以描述为以下形式:

1. [
2. 'class' =&gt; 'ClassName',
3. 'propertyName' =&gt; 'propertyValue',
4. 'on eventName' =&gt; $eventHandler,
5. 'as behaviorName' =&gt; $behaviorConfig,
6. ]

其中

  • class 元素指定了将要创建的对象的完全限定类名。
  • propertyName 元素指定了对象属性的初始值。键名是属性名,值是该属性对应的初始值。只有公共成员变量以及通过 getter/setter 定义的属性可以被配置。
  • on eventName 元素指定了附加到对象事件上的句柄是什么。请注意,数组的键名由 on 前缀加事件名组成。请参考事件章节了解事件句柄格式。
  • as behaviorName 元素指定了附加到对象的行为。请注意,数组的键名由 as 前缀加行为名组成。$behaviorConfig 表示创建行为的配置信息,格式与我们现在总体叙述的配置格式一样。

下面是一个配置了初始化属性值,事件句柄和行为的示例:

1. [
2. 'class' =&gt; 'app\\components\\SearchEngine',
3. 'apiKey' =&gt; 'xxxxxxxx',
4. 'on search' =&gt; function ($event) {
5. Yii::info("搜索的关键词: " . $event-&gt;keyword);
6. },
7. 'as indexer' =&gt; [
8. 'class' =&gt; 'app\\components\\IndexerBehavior',
9. // ... 初始化属性值 ...
10. ],
11. ]

使用配置

Yii 中的配置可以用在很多场景。本章开头我们展示了如何使用 Yii::creatObject() 根据配置信息创建对象。本小节将介绍配置的两种主要用法 —— 配置应用与配置小部件。

应用的配置

应用的配置可能是最复杂的配置之一。因为 yii\web\Application|application 类拥有很多可配置的属性和事件。更重要的是它的 yii\web\Application::components|components 属性可以接收配置数组并通过应用注册为组件。以下是一个针对基础应用模板的应用配置概要:

1. $config = [
2. 'id' =&gt; 'basic',
3. 'basePath' =&gt; dirname(__DIR__),
4. 'extensions' =&gt; require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
5. 'components' =&gt; [
6. 'cache' =&gt; [
7. 'class' =&gt; 'yii\\caching\\FileCache',
8. ],
9. 'mailer' =&gt; [
10. 'class' =&gt; 'yii\\swiftmailer\\Mailer',
11. ],
12. 'log' =&gt; [
13. 'class' =&gt; 'yii\\log\\Dispatcher',
14. 'traceLevel' =&gt; YII_DEBUG ? 3 : 0,
15. 'targets' =&gt; [
16. [
17. 'class' =&gt; 'yii\\log\\FileTarget',
18. ],
19. ],
20. ],
21. 'db' =&gt; [
22. 'class' =&gt; 'yii\\db\\Connection',
23. 'dsn' =&gt; 'mysql:host=localhost;dbname=stay2',
24. 'username' =&gt; 'root',
25. 'password' =&gt; '',
26. 'charset' =&gt; 'utf8',
27. ],
28. ],
29. ];

配置中没有 class 键的原因是这段配置应用在下面的入口脚本中,类名已经指定了。

1. (new yii\\web\\Application($config))-&gt;run();

更多关于应用 components 属性配置的信息可以查阅应用以及服务定位器章节。

小部件的配置

使用小部件时,常常需要配置以便自定义其属性。 yii\base\Widget::widget() 和 yii\base\Widget::beginWidget() 方法都可以用来创建小部件。它们可以接受配置数组:

1. use yii\\widgets\\Menu;

3. echo Menu::widget([
4. 'activateItems' =&gt; false,
5. 'items' =&gt; [
6. ['label' =&gt; 'Home', 'url' =&gt; ['site/index'`,
7. ['label' =&gt; 'Products', 'url' =&gt; ['product/index'`,
8. ['label' =&gt; 'Login', 'url' =&gt; ['site/login'], 'visible' =&gt; Yii::$app-&gt;user-&gt;isGuest],
9. ],
10. ]);

上述代码创建了一个小部件 Menu 并将其 activateItems 属性初始化为 false。item 属性也配置成了将要显示的菜单条目。

请注意,代码中已经给出了类名 yii\widgets\Menu,配置数组不应该再包含 class 键。

配置文件

当配置的内容十分复杂,通用做法是将其存储在一或多个 PHP 文件中,这些文件被称为配置文件。一个配置文件返回的是 PHP 数组。例如,像这样把应用配置信息存储在名为 web.php 的文件中:

1. return [
2. 'id' =&gt; 'basic',
3. 'basePath' =&gt; dirname(__DIR__),
4. 'extensions' =&gt; require(__DIR__ . '/../vendor/yiisoft/extensions.php'),
5. 'components' =&gt; require(__DIR__ . '/components.php'),
6. ];

鉴于 components 配置也很复杂,上述代码把它们存储在单独的 components.php 文件中,并且包含在web.php 里。components.php 的内容如下:

1. return [
2. 'cache' =&gt; [
3. 'class' =&gt; 'yii\\caching\\FileCache',
4. ],
5. 'mailer' =&gt; [
6. 'class' =&gt; 'yii\\swiftmailer\\Mailer',
7. ],
8. 'log' =&gt; [
9. 'class' =&gt; 'yii\\log\\Dispatcher',
10. 'traceLevel' =&gt; YII_DEBUG ? 3 : 0,
11. 'targets' =&gt; [
12. [
13. 'class' =&gt; 'yii\\log\\FileTarget',
14. ],
15. ],
16. ],
17. 'db' =&gt; [
18. 'class' =&gt; 'yii\\db\\Connection',
19. 'dsn' =&gt; 'mysql:host=localhost;dbname=stay2',
20. 'username' =&gt; 'root',
21. 'password' =&gt; '',
22. 'charset' =&gt; 'utf8',
23. ],
24. ];

仅仅需要 “require”,就可以取得一个配置文件的配置内容,像这样:

1. $config = require('path/to/web.php');
2. (new yii\\web\\Application($config))-&gt;run();

默认配置

Yii::createObject() 方法基于依赖注入容器实现。使用 Yii::creatObject() 创建对象时,可以附加一系列默认配置到指定类的任何实例。默认配置还可以在入口脚本中调用 Yii::$container-&gt;set() 来定义。

例如,如果你想自定义 yii\widgets\LinkPager 小部件,以便让分页器最多只显示 5 个翻页按钮(默认是 10 个),你可以用下述代码实现:

1. \\Yii::$container-&gt;set('yii\\widgets\\LinkPager', [
2. 'maxButtonCount' =&gt; 5,
3. ]);

不使用默认配置的话,你就得在任何使用分页器的地方,都配置 maxButtonCount 的值。

环境常量

配置经常要随着应用运行的不同环境更改。例如在开发环境中,你可能使用名为 mydb_dev 的数据库,而生产环境则使用 mydb_prod 数据库。为了便于切换使用环境,Yii 提供了一个定义在入口脚本中的 YII_ENV 常量。如下:

1. defined('YII_ENV') or define('YII_ENV', 'dev');

你可以把 YII_ENV 定义成以下任何一种值:

  • prod:生产环境。常量 YII_ENV_PROD 将被看作 true。如果你没修改过,这就是 YII_ENV 的默认值。
  • dev:开发环境。常量 YII_ENV_DEV 将被看作 true。
  • test:测试环境。常量 YII_ENV_TEST 将被看作 true。

有了这些环境常量,你就可以根据当下应用运行环境的不同,进行差异化配置。例如,应用可以包含下述代码只在开发环境中开启调试工具

1. $config = [...];

3. if (YII_ENV_DEV) {
4. // 根据 `dev` 环境进行的配置调整
5. $config['bootstrap'][] = 'debug';
6. $config['modules']['debug'] = 'yii\\debug\\Module';
7. }

9. return $config;

别名(Aliases) - Yii2教程

别名(Aliases)

别名用来表示文件路径和 URL,这样就避免了在代码中硬编码一些绝对路径和 URL。一个别名必须以 @ 字符开头,以区别于传统的文件路径和 URL。Yii 预定义了大量可用的别名。例如,别名 @yii 指的是 Yii 框架本身的安装目录,而 @web 表示的是当前运行应用的根 URL。

定义别名

你可以调用 Yii::setAlias() 来给文件路径或 URL 定义别名:

1. // 文件路径的别名
2. Yii::setAlias('@foo', '/path/to/foo');

4. // URL 的别名
5. Yii::setAlias('@bar', 'http://www.example.com');

注意:别名所指向的文件路径或 URL 不一定是真实存在的文件或资源。

可以通过在一个别名后面加斜杠 / 和一至多个路径分段生成新别名(无需调用 Yii::setAlias())。我们把通过 Yii::setAlias() 定义的别名称为根别名,而用他们衍生出去的别名成为衍生别名。例如,@foo 就是跟别名,而 @foo/bar/file.php 是一个衍生别名。

你还可以用别名去定义新别名(根别名与衍生别名均可):

1. Yii::setAlias('@foobar', '@foo/bar');

根别名通常在引导阶段定义。比如你可以在入口脚本里调用 Yii::setAlias()。为了方便起见,应用提供了一个名为 aliases 的可写属性,你可以在应用配置中设置它,就像这样:

1. return [
2. // ...
3. 'aliases' =&gt; [
4. '@foo' =&gt; '/path/to/foo',
5. '@bar' =&gt; 'http://www.example.com',
6. ],
7. ];

解析别名

你可以调用 Yii::getAlias() 命令来解析根别名到对应的文件路径或 URL。同样的页面也可以用于解析衍生别名。例如:

1. echo Yii::getAlias('@foo'); // 输出:/path/to/foo
2. echo Yii::getAlias('@bar'); // 输出:http://www.example.com
3. echo Yii::getAlias('@foo/bar/file.php'); // 输出:/path/to/foo/bar/file.php

由衍生别名所解析出的文件路径和 URL 是通过替换掉衍生别名中的根别名部分得到的。

注意:Yii::getAlias() 并不检查结果路径/URL 所指向的资源是否真实存在。

根别名可能也会包含斜杠 /。Yii::getAlias() 足够智能到判断一个别名中的哪部分是根别名,因此能正确解析文件路径/URL。例如:

1. Yii::setAlias('@foo', '/path/to/foo');
2. Yii::setAlias('@foo/bar', '/path2/bar');
3. echo Yii::getAlias('@foo/test/file.php'); // 输出:/path/to/foo/test/file.php
4. echo Yii::getAlias('@foo/bar/file.php'); // 输出:/path2/bar/file.php

若 @foo/bar 未被定义为根别名,最后一行语句会显示为 /path/to/foo/bar/file.php。

使用别名

别名在 Yii 的很多地方都会被正确识别,无需调用 Yii::getAlias() 来把它们转换为路径/URL。例如,yii\caching\FileCache::cachePath 能同时接受文件路径或是指向文件路径的别名,因为通过 @ 前缀能区分它们。

1. use yii\\caching\\FileCache;

3. $cache = new FileCache([
4. 'cachePath' =&gt; '@runtime/cache',
5. ]);

请关注 API 文档了解特定属性或方法参数是否支持别名。

预定义的别名

Yii 预定义了一系列别名来简化常用路径和 URL的使用:

  • @yii - BaseYii.php 文件所在的目录(也被称为框架安装目录)
  • @app - 当前运行的应用 yii\base\Application::basePath|根路径(base path)
  • @runtime - 当前运行的应用的 yii\base\Application::runtimePath|运行环境(runtime)路径
  • @vendor - yii\base\Application::vendorPath|Composer 供应商目录
  • @webroot - 当前运行应用的 Web 入口目录
  • @web - 当前运行应用的根 URL

@yii 别名是在入口脚本里包含 Yii.php 文件时定义的,其他的别名都是在配置应用的时候,于应用的构造方法内定义的。

扩展的别名

每一个通过 Composer 安装的 扩展 都自动添加了一个别名。该别名会以该扩展在 composer.json 文件中所声明的根命名空间为名,且他直接代指该包的根目录。例如,如果你安装有 yiisoft/yii2-jui 扩展,会自动得到 @yii/jui 别名,它定义于引导启动阶段:

1. Yii::setAlias('@yii/jui', 'VendorPath/yiisoft/yii2-jui');

服务定位器 - Yii2教程

服务定位器

服务定位器是一个了解如何提供各种应用所需的服务(或组件)的对象。在服务定位器中,每个组件都只有一个单独的实例,并通过ID 唯一地标识。用这个 ID 就能从服务定位器中得到这个组件。

在 Yii 中,服务定位器是 yii\di\ServiceLocator 或其子类的一个实例。

最常用的服务定位器是application(应用)对象,可以通过 \Yii::$app 访问。它所提供的服务被称为application components(应用组件),比如:request、response、urlManager 组件。可以通过服务定位器所提供的功能,非常容易地配置这些组件,或甚至是用你自己的实现替换掉他们。

除了 application 对象,每个模块对象本身也是一个服务定位器。

要使用服务定位器,第一步是要注册相关组件。组件可以通过 yii\di\ServiceLocator::set() 方法进行注册。以下的方法展示了注册组件的不同方法:

1. use yii\\di\\ServiceLocator;
2. use yii\\caching\\FileCache;

4. $locator = new ServiceLocator;

6. // 通过一个可用于创建该组件的类名,注册 "cache" (缓存)组件。
7. $locator-&gt;set('cache', 'yii\\caching\\ApcCache');

9. // 通过一个可用于创建该组件的配置数组,注册 "db" (数据库)组件。
10. $locator-&gt;set('db', [
11. 'class' =&gt; 'yii\\db\\Connection',
12. 'dsn' =&gt; 'mysql:host=localhost;dbname=demo',
13. 'username' =&gt; 'root',
14. 'password' =&gt; '',
15. ]);

17. // 通过一个能返回该组件的匿名函数,注册 "search" 组件。
18. $locator-&gt;set('search', function () {
19. return new app\\components\\SolrService;
20. });

22. // 用组件注册 "pageCache" 组件
23. $locator-&gt;set('pageCache', new FileCache);

一旦组件被注册成功,你可以任选以下两种方式之一,通过它的 ID 访问它:

1. $cache = $locator-&gt;get('cache');
2. // 或者
3. $cache = $locator-&gt;cache;

如上所示, yii\di\ServiceLocator 允许通过组件 ID 像访问一个属性值那样访问一个组件。当你第一次访问某组件时,yii\di\ServiceLocator 会通过该组件的注册信息创建一个该组件的实例,并返回它。之后,如果再次访问,则服务定位器会返回同一个实例。

你可以通过 yii\di\ServiceLocator::has() 检查某组件 ID 是否被注册。若你用一个无效的 ID 调用 yii\di\ServiceLocator::get(),则会抛出一个异常。

因为服务定位器,经常会在创建时附带配置信息,因此我们提供了一个可写的属性,名为 yii\di\ServiceLocator::setComponents()|components,这样就可以配置该属性,或一次性注册多个组件。下面的代码展示了如何用一个配置数组,配置一个应用并注册"db","cache" 和 "search" 三个组件:

1. return [
2. // ...
3. 'components' =&gt; [
4. 'db' =&gt; [
5. 'class' =&gt; 'yii\\db\\Connection',
6. 'dsn' =&gt; 'mysql:host=localhost;dbname=demo',
7. 'username' =&gt; 'root',
8. 'password' =&gt; '',
9. ],
10. 'cache' =&gt; 'yii\\caching\\ApcCache',
11. 'search' =&gt; function () {
12. return new app\\components\\SolrService;
13. },
14. ],
15. ];

依赖注入容器 - Yii2教程

依赖注入容器

依赖注入(Dependency Injection,DI)容器就是一个对象,它知道怎样初始化并配置对象及其依赖的所有对象。Martin 的文章 已经解释了 DI 容器为什么很有用。这里我们主要讲解 Yii 提供的 DI 容器的使用方法。

依赖注入

Yii 通过 yii\di\Container 类提供 DI 容器特性。它支持如下几种类型的依赖注入:

  • 构造方法注入;
  • Setter 和属性注入;
  • PHP 回调注入.

构造方法注入

在参数类型提示的帮助下,DI 容器实现了构造方法注入。当容器被用于创建一个新对象时,类型提示会告诉它要依赖什么类或接口。容器会尝试获取它所依赖的类或接口的实例,然后通过构造器将其注入新的对象。例如:

1. class Foo
2. {
3. public function __construct(Bar $bar)
4. {
5. }
6. }

8. $foo = $container-&gt;get('Foo');
9. // 上面的代码等价于:
10. $bar = new Bar;
11. $foo = new Foo($bar);

Setter 和属性注入

Setter 和属性注入是通过配置提供支持的。当注册一个依赖或创建一个新对象时,你可以提供一个配置,该配置会提供给容器用于通过相应的 Setter 或属性注入依赖。例如:

1. use yii\\base\\Object;

3. class Foo extends Object
4. {
5. public $bar;

7. private $_qux;

9. public function getQux()
10. {
11. return $this-&gt;_qux;
12. }

14. public function setQux(Qux $qux)
15. {
16. $this-&gt;_qux = $qux;
17. }
18. }

20. $container-&gt;get('Foo', [], [
21. 'bar' =&gt; $container-&gt;get('Bar'),
22. 'qux' =&gt; $container-&gt;get('Qux'),
23. ]);

PHP 回调注入

这种情况下,容器将使用一个注册过的 PHP 回调创建一个类的新实例。回调负责解决依赖并将其恰当地注入新创建的对象。例如:

1. $container-&gt;set('Foo', function () {
2. return new Foo(new Bar);
3. });

5. $foo = $container-&gt;get('Foo');

注册依赖关系

可以用 yii\di\Container::set() 注册依赖关系。注册会用到一个依赖关系名称和一个依赖关系的定义。依赖关系名称可以是一个类名,一个接口名或一个别名。依赖关系的定义可以是一个类名,一个配置数组,或者一个 PHP 回调。

1. $container = new \\yii\\di\\Container;

3. // 注册一个同类名一样的依赖关系,这个可以省略。
4. $container-&gt;set('yii\\db\\Connection');

6. // 注册一个接口
7. // 当一个类依赖这个接口时,相应的类会被初始化作为依赖对象。
8. $container-&gt;set('yii\\mail\\MailInterface', 'yii\\swiftmailer\\Mailer');

10. // 注册一个别名。
11. // 你可以使用 $container-&gt;get('foo') 创建一个 Connection 实例
12. $container-&gt;set('foo', 'yii\\db\\Connection');

14. // 通过配置注册一个类
15. // 通过 get() 初始化时,配置将会被使用。
16. $container-&gt;set('yii\\db\\Connection', [
17. 'dsn' =&gt; 'mysql:host=127.0.0.1;dbname=demo',
18. 'username' =&gt; 'root',
19. 'password' =&gt; '',
20. 'charset' =&gt; 'utf8',
21. ]);

23. // 通过类的配置注册一个别名
24. // 这种情况下,需要通过一个 “class” 元素指定这个类
25. $container-&gt;set('db', [
26. 'class' =&gt; 'yii\\db\\Connection',
27. 'dsn' =&gt; 'mysql:host=127.0.0.1;dbname=demo',
28. 'username' =&gt; 'root',
29. 'password' =&gt; '',
30. 'charset' =&gt; 'utf8',
31. ]);

33. // 注册一个 PHP 回调
34. // 每次调用 $container-&gt;get('db') 时,回调函数都会被执行。
35. $container-&gt;set('db', function ($container, $params, $config) {
36. return new \\yii\\db\\Connection($config);
37. });

39. // 注册一个组件实例
40. // $container-&gt;get('pageCache') 每次被调用时都会返回同一个实例。
41. $container-&gt;set('pageCache', new FileCache);

Tip: 如果依赖关系名称和依赖关系的定义相同,则不需要通过 DI 容器注册该依赖关系。

通过 set() 注册的依赖关系,在每次使用时都会产生一个新实例。可以使用 yii\di\Container::setSingleton() 注册一个单例的依赖关系:

1. $container-&gt;setSingleton('yii\\db\\Connection', [
2. 'dsn' =&gt; 'mysql:host=127.0.0.1;dbname=demo',
3. 'username' =&gt; 'root',
4. 'password' =&gt; '',
5. 'charset' =&gt; 'utf8',
6. ]);

解决依赖关系

注册依赖关系后,就可以使用 DI 容器创建新对象了。容器会自动解决依赖关系,将依赖实例化并注入新创建的对象。依赖关系的解决是递归的,如果一个依赖关系中还有其他依赖关系,则这些依赖关系都会被自动解决。

可以使用 yii\di\Container::get() 创建新的对象。该方法接收一个依赖关系名称,它可以是一个类名,一个接口名或一个别名。依赖关系名或许是通过 set() 或 setSingleton() 注册的。你可以随意地提供一个类的构造器参数列表和一个configuration 用于配置新创建的对象。例如:

1. // "db" 是前面定义过的一个别名
2. $db = $container-&gt;get('db');

4. // 等价于: $engine = new \\app\\components\\SearchEngine($apiKey, ['type' =&gt; 1]);
5. $engine = $container-&gt;get('app\\components\\SearchEngine', [$apiKey], ['type' =&gt; 1]);

代码背后,DI 容器做了比创建对象多的多的工作。容器首先将检查类的构造方法,找出依赖的类或接口名,然后自动递归解决这些依赖关系。

如下代码展示了一个更复杂的示例。UserLister 类依赖一个实现了 UserFinderInterface 接口的对象;UserFinder 类实现了这个接口,并依赖于一个 Connection 对象。所有这些依赖关系都是通过类构造器参数的类型提示定义的。通过属性依赖关系的注册,DI 容器可以自动解决这些依赖关系并能通过一个简单的get('userLister') 调用创建一个新的 UserLister 实例。

1. namespace app\\models;

3. use yii\\base\\Object;
4. use yii\\db\\Connection;
5. use yii\\di\\Container;

7. interface UserFinderInterface
8. {
9. function findUser();
10. }

12. class UserFinder extends Object implements UserFinderInterface
13. {
14. public $db;

16. public function __construct(Connection $db, $config = [])
17. {
18. $this-&gt;db = $db;
19. parent::__construct($config);
20. }

22. public function findUser()
23. {
24. }
25. }

27. class UserLister extends Object
28. {
29. public $finder;

31. public function __construct(UserFinderInterface $finder, $config = [])
32. {
33. $this-&gt;finder = $finder;
34. parent::__construct($config);
35. }
36. }

38. $container = new Container;
39. $container-&gt;set('yii\\db\\Connection', [
40. 'dsn' =&gt; '...',
41. ]);
42. $container-&gt;set('app\\models\\UserFinderInterface', [
43. 'class' =&gt; 'app\\models\\UserFinder',
44. ]);
45. $container-&gt;set('userLister', 'app\\models\\UserLister');

47. $lister = $container-&gt;get('userLister');

49. // 等价于:

51. $db = new \\yii\\db\\Connection(['dsn' =&gt; '...']);
52. $finder = new UserFinder($db);
53. $lister = new UserLister($finder);

实践中的运用

当在应用程序的入口脚本中引入 Yii.php 文件时,Yii 就创建了一个 DI 容器。这个 DI 容器可以通过 Yii::$container 访问。当调用 Yii::createObject() 时,此方法实际上会调用这个容器的 yii\di\Container::get()|get() 方法创建新对象。如上所述,DI 容器会自动解决依赖关系(如果有)并将其注入新创建的对象中。因为 Yii 在其多数核心代码中都使用了 Yii::createObject() 创建新对象,所以你可以通过 Yii::$container 全局性地自定义这些对象。

例如,你可以全局性自定义 yii\widgets\LinkPager 中分页按钮的默认数量:

1. \\Yii::$container-&gt;set('yii\\widgets\\LinkPager', ['maxButtonCount' =&gt; 5]);

这样如果你通过如下代码在一个视图里使用这个挂件,它的 maxButtonCount 属性就会被初始化为 5 而不是类中定义的默认值 10。

1. echo \\yii\\widgets\\LinkPager::widget();

然而你依然可以覆盖通过 DI 容器设置的值:

1. echo \\yii\\widgets\\LinkPager::widget(['maxButtonCount' =&gt; 20]);

另一个例子是借用 DI 容器中自动构造方法注入带来的好处。假设你的控制器类依赖一些其他对象,例如一个旅馆预订服务。你可以通过一个构造器参数声明依赖关系,然后让 DI 容器帮你自动解决这个依赖关系。

1. namespace app\\controllers;

3. use yii\\web\\Controller;
4. use app\\components\\BookingInterface;

6. class HotelController extends Controller
7. {
8. protected $bookingService;

10. public function __construct($id, $module, BookingInterface $bookingService, $config = [])
11. {
12. $this-&gt;bookingService = $bookingService;
13. parent::__construct($id, $module, $config);
14. }
15. }

如果你从浏览器中访问这个控制器,你将看到一个报错信息,提醒你 BookingInterface 无法被实例化。这是因为你需要告诉 DI 容器怎样处理这个依赖关系。

1. \\Yii::$container-&gt;set('app\\components\\BookingInterface', 'app\\components\\BookingService');

现在如果你再次访问这个控制器,一个 app\components\BookingService 的实例就会被创建并被作为第三个参数注入到控制器的构造器中。

什么时候注册依赖关系

由于依赖关系在创建新对象时需要解决,因此它们的注册应该尽早完成。如下是推荐的实践:

  • 如果你是一个应用程序的开发者,你可以在应用程序的入口脚本或者被入口脚本引入的脚本中注册依赖关系。
  • 如果你是一个可再分发扩展的开发者,你可以将依赖关系注册到扩展的引导类中。

总结

依赖注入和服务定位器都是流行的设计模式,它们使你可以用充分解耦且更利于测试的风格构建软件。强烈推荐你阅读 Martin 的文章 ,对依赖注入和服务定位器有个更深入的理解。

Yii 在依赖住入(DI)容器之上实现了它的服务定位器。当一个服务定位器尝试创建一个新的对象实例时,它会把调用转发到 DI 容器。后者将会像前文所述那样自动解决依赖关系。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论