diff --git a/docs/guide-zh-CN/concept-aliases.md b/docs/guide-zh-CN/concept-aliases.md index b85e0be..87c8c41 100644 --- a/docs/guide-zh-CN/concept-aliases.md +++ b/docs/guide-zh-CN/concept-aliases.md @@ -1,12 +1,15 @@ 别名(Aliases) -======= +============= 别名用来表示文件路径和 URL,这样就避免了在代码中硬编码一些绝对路径和 URL。 一个别名必须以 `@` 字符开头,以区别于传统的文件路径和 URL。 -Yii 预定义了大量可用的别名。例如,别名 `@yii` 指的是 Yii 框架本身的安装目录,而 `@web` 表示的是当前运行应用的根 URL。 +没有前导 `@` 定义的别名将以 `@` 字符作为前缀。 -定义别名 ----------------- +Yii 预定义了大量可用的别名。例如,别名 `@yii` 指的是 Yii 框架本身的安装目录, +而 `@web` 表示的是当前运行应用的根 URL。 + +定义别名(Defining Aliases) +------------------------- 你可以调用 [[Yii::setAlias()]] 来给文件路径或 URL 定义别名: @@ -16,6 +19,9 @@ Yii::setAlias('@foo', '/path/to/foo'); // URL 的别名 Yii::setAlias('@bar', 'http://www.example.com'); + +// 包含 \foo\Bar 类的具体文件的别名 +Yii::setAlias('@foo/Bar.php', '/definitely/not/foo/Bar.php'); ``` > Note: 别名所指向的文件路径或 URL 不一定是真实存在的文件或资源。 @@ -47,8 +53,8 @@ return [ ``` -解析别名 ------------------ +解析别名(Resolving Aliases) +-------------------------- 你可以调用 [[Yii::getAlias()]] 命令来解析根别名到对应的文件路径或 URL。 同样的页面也可以用于解析衍生别名。例如: @@ -79,8 +85,8 @@ echo Yii::getAlias('@foo/bar/file.php'); // 输出:/path2/bar/file.php 若 `@foo/bar` 未被定义为根别名,最后一行语句会显示为 `/path/to/foo/bar/file.php`。 -使用别名 -------------- +使用别名(Using Aliases) +---------------------- 别名在 Yii 的很多地方都会被正确识别, 无需调用 [[Yii::getAlias()]] 来把它们转换为路径/URL。 @@ -98,8 +104,8 @@ $cache = new FileCache([ 请关注 API 文档了解特定属性或方法参数是否支持别名。 -预定义的别名 ------------------- +预定义的别名(Predefined Aliases) +------------------------------ Yii 预定义了一系列别名来简化常用路径和 URL 的使用: @@ -118,8 +124,8 @@ Yii 预定义了一系列别名来简化常用路径和 URL 的使用: 于应用的构造方法内定义的。 -扩展的别名 ------------------ +扩展的别名(Extension Aliases) +---------------------------- 每一个通过 Composer 安装的 [扩展](structure-extensions.md) 都自动添加了一个别名。 该别名会以该扩展在 `composer.json` 文件中所声明的根命名空间为名, diff --git a/docs/guide-zh-CN/concept-components.md b/docs/guide-zh-CN/concept-components.md index 14bc690..3b8b82a 100644 --- a/docs/guide-zh-CN/concept-components.md +++ b/docs/guide-zh-CN/concept-components.md @@ -1,5 +1,5 @@ 组件(Component) -========== +============== 组件是 Yii 应用的主要基石。是 [[yii\base\Component]] 类或其子类的实例。 三个用以区分它和其它类的主要功能有: @@ -35,10 +35,10 @@ echo DatePicker::widget([ 当继承 [[yii\base\Component]] 或 [[yii\base\Object]] 时, 推荐你使用如下的编码风格: -- 若你需要重写构造方法(Constructor),传入 `$config` 作为构造器方法**最后一个**参数, +- 若你需要重写构造方法(Constructor),传入 `$config` 作为构造器方法*最后一个*参数, 然后把它传递给父类的构造方法。 -- 永远在你重写的构造方法**结尾处**调用一下父类的构造方法。 -- 如果你重写了 [[yii\base\BaseObject::init()]] 方法,请确保你在 `init` 方法的**开头处**调用了父类的 `init` 方法。 +- 永远在你重写的构造方法*结尾处*调用一下父类的构造方法。 +- 如果你重写了 [[yii\base\BaseObject::init()]] 方法,请确保你在 `init` 方法的*开头处*调用了父类的 `init` 方法。 例子如下: @@ -56,7 +56,7 @@ class MyClass extends BaseObject public function __construct($param1, $param2, $config = []) { - // ... initialization before configuration is applied + // ... 在应用配置之前初始化 parent::__construct($config); } @@ -65,7 +65,7 @@ class MyClass extends BaseObject { parent::init(); - // ... initialization after configuration is applied + // ... 应用配置后进行初始化 } } ``` @@ -93,6 +93,5 @@ $component = \Yii::createObject([ 3. 在 [[yii\base\BaseObject::init()|init()]] 方法内进行初始化后的收尾工作。你可以通过重写此方法,进行一些良品检验,属性的初始化之类的工作。 4. 对象方法调用。 -前三步都是在对象的构造方法内发生的。这意味着一旦你获得了一个对象实例, +前三步都是在对象的构造方法内发生的。这意味着一旦你获得了一个对象实例(即一个对象), 那么它就已经初始化就绪可供使用。 - diff --git a/docs/guide-zh-CN/concept-configurations.md b/docs/guide-zh-CN/concept-configurations.md index 4512cff..33604b9 100644 --- a/docs/guide-zh-CN/concept-configurations.md +++ b/docs/guide-zh-CN/concept-configurations.md @@ -1,5 +1,5 @@ -配置 -============= +配置(Configurations) +==================== 在 Yii 中,创建新对象和初始化已存在对象时广泛使用配置。 配置通常包含被创建对象的类名和一组将要赋值给对象 @@ -35,7 +35,7 @@ Yii::configure($object, $config); 请注意,如果配置一个已存在的对象,那么配置数组中不应该包含指定类名的 `class` 元素。 -## 配置的格式 +## 配置的格式(Configuration Format) 一个配置的格式可以描述为以下形式: @@ -78,14 +78,14 @@ Yii::configure($object, $config); ``` -## 使用配置 +## 使用配置(Using Configurations) Yii 中的配置可以用在很多场景。本章开头我们展示了如何使用 [[Yii::creatObject()]] 根据配置信息创建对象。本小节将介绍配置的两种 主要用法 —— 配置应用与配置小部件。 -### 应用的配置 +### 应用的配置(Application Configurations) [应用](structure-applications.md)的配置可能是最复杂的配置之一。 因为 [[yii\web\Application|application]] 类拥有很多可配置的属性和事件。 @@ -158,7 +158,7 @@ $config = [ 获取更多 `definitions` 和 `singletons` 配置项和实际使用的例子。 -### 小部件的配置 +### 小部件的配置(Widget Configurations) 使用[小部件](structure-widgets.md)时,常常需要配置以便自定义其属性。 [[yii\base\Widget::widget()]] 和 [[yii\base\Widget::begin()]] 方法都可以用来创建小部件。 @@ -183,10 +183,10 @@ echo Menu::widget([ 请注意,代码中已经给出了类名 `yii\widgets\Menu`,配置数组**不应该**再包含 `class` 键。 -## 配置文件 +## 配置文件(Configuration Files) 当配置的内容十分复杂,通用做法是将其存储在一或多个 PHP 文件中, -这些文件被称为**配置文件**。一个配置文件返回的是 PHP 数组。 +这些文件被称为*配置文件*。一个配置文件返回的是 PHP 数组。 例如,像这样把应用配置信息存储在名为 `web.php` 的文件中: ```php @@ -236,7 +236,7 @@ $config = require 'path/to/web.php'; ``` -## 默认配置 +## 默认配置(Default Configurations) [[Yii::createObject()]] 方法基于[依赖注入容器](concept-di-container.md)实现。 使用 [[Yii::creatObject()]] 创建对象时,可以附加一系列**默认配置**到指定类的任何实例。 @@ -256,7 +256,7 @@ $config = require 'path/to/web.php'; 都配置 `maxButtonCount` 的值。 -## 环境常量 +## 环境常量(Environment Constants) 配置经常要随着应用运行的不同环境更改。例如在开发环境中, 你可能使用名为 `mydb_dev` 的数据库, diff --git a/docs/guide-zh-CN/concept-di-container.md b/docs/guide-zh-CN/concept-di-container.md index 3fbba38..511cb80 100644 --- a/docs/guide-zh-CN/concept-di-container.md +++ b/docs/guide-zh-CN/concept-di-container.md @@ -1,13 +1,13 @@ -依赖注入容器 -========== +依赖注入容器(Dependency Injection Container) +=========================================== 依赖注入(Dependency Injection,DI)容器就是一个对象,它知道怎样初始化并配置对象及其依赖的所有对象。 [Martin 的文章](http://martinfowler.com/articles/injection.html) 已经解释了 DI 容器为什么很有用。 这里我们主要讲解 Yii 提供的 DI 容器的使用方法。 -依赖注入 -------- +依赖注入(Dependency Injection) +----------------------------- Yii 通过 [[yii\di\Container]] 类提供 DI 容器特性。 它支持如下几种类型的依赖注入: @@ -18,7 +18,7 @@ Yii 通过 [[yii\di\Container]] 类提供 DI 容器特性。 * PHP 回调注入. -### 构造方法注入 +### 构造方法注入(Constructor Injection) 在参数类型提示的帮助下,DI 容器实现了构造方法注入。当容器被用于创建一个新对象时, 类型提示会告诉它要依赖什么类或接口。 @@ -40,7 +40,7 @@ $foo = new Foo($bar); ``` -### 方法注入 +### 方法注入(Method Injection) 通常,类的依赖关系传递给构造函数,并且在整个生命周期中都可以在类内部使用。 通过方法注入,可以提供仅由类的单个方法需要的依赖关系, @@ -70,7 +70,7 @@ $obj = new MyClass(/*...*/); Yii::$container->invoke([$obj, 'doSomething'], ['param1' => 42]); // $something will be provided by the DI container ``` -### Setter 和属性注入 +### Setter 和属性注入(Setter and Property Injection) Setter 和属性注入是通过[配置](concept-configurations.md)提供支持的。 当注册一个依赖或创建一个新对象时,你可以提供一个配置, @@ -109,7 +109,7 @@ $container->get('Foo', [], [ 否则,将在创建对象*后*应用该配置。 -### PHP 回调注入 +### PHP 回调注入(PHP Callable Injection) 在这种情况下,容器将使用已注册的 PHP 回调来构建类的新实例。 每次调用 [[yii\di\Container::get()]] ,相应的回调将被调用。 @@ -119,7 +119,7 @@ $container->get('Foo', [], [ ```php $container->set('Foo', function () { $foo = new Foo(new Bar); - // ... other initializations ... + // ... 其他初始化 ... return $foo; }); @@ -134,7 +134,7 @@ class FooBuilder public static function build() { $foo = new Foo(new Bar); - // ... other initializations ... + // ... 其他初始化 ... return $foo; } } @@ -147,8 +147,8 @@ $foo = $container->get('Foo'); 这样做的话,想要配置 `Foo` 类的人不再需要知道它是如何构建的。 -注册依赖关系 ------------------------- +注册依赖关系(Registering Dependencies) +------------------------------------ 可以用 [[yii\di\Container::set()]] 注册依赖关系。注册会用到一个依赖关系名称和一个依赖关系的定义。 依赖关系名称可以是一个类名,一个接口名或一个别名。 @@ -216,8 +216,8 @@ $container->setSingleton('yii\db\Connection', [ ``` -解决依赖关系 ----------------------- +解决依赖关系(Resolving Dependencies) +---------------------------------- 注册依赖关系后,就可以使用 DI 容器创建新对象了。容器会自动解决依赖关系, 将依赖实例化并注入新创建的对象。依赖关系的解决是递归的, @@ -307,8 +307,8 @@ $lister = new UserLister($finder); ``` -实践中的运用 ---------------- +实践中的运用(Practical Usage) +--------------------------- 当在应用程序的[入口脚本](structure-entry-scripts.md)中引入 `Yii.php` 文件时, Yii 就创建了一个 DI 容器。这个 DI 容器可以通过 [[Yii::$container]] 访问。 @@ -372,16 +372,16 @@ class HotelController extends Controller 现在如果你再次访问这个控制器,一个 `app\components\BookingService` 的实例就会被创建并被作为第三个参数注入到控制器的构造器中。 -高级实用性 ---------- +高级实用性(Advanced Practical Usage) +------------------------------------ 比如说我们在 API 应用方面工作: -- `app\components\Request` class that extends `yii\web\Request` and provides additional functionality -- `app\components\Response` class that extends `yii\web\Response` and should have `format` property - set to `json` on creation -- `app\storage\FileStorage` and `app\storage\DocumentsReader` classes that implement some logic on - working with documents that are located in some file storage: +- `app\components\Request` 类继承了 `yii\web\Request` 并提供了额外的功能 +- `app\components\Response` 类继承了 `yii\web\Response` 并且在创建时应该将 `format` + 属性设置为 `json` +- `app\storage\FileStorage` 和 `app\storage\DocumentsReader` + 用于处理位于某些文件存储中的文档的某些逻辑: ```php class FileStorage @@ -399,18 +399,18 @@ class HotelController extends Controller } ``` -It is possible to configure multiple definitions at once, passing configuration array to -[[yii\di\Container::setDefinitions()|setDefinitions()]] or [[yii\di\Container::setSingletons()|setSingletons()]] method. -Iterating over the configuration array, the methods will call [[yii\di\Container::set()|set()]] -or [[yii\di\Container::setSingleton()|setSingleton()]] respectively for each item. +可以一次配置多个定义,将配置数组传递给 +[[yii\di\Container::setDefinitions()|setDefinitions()]] 或 [[yii\di\Container::setSingletons()|setSingletons()]] 方法。 +遍历配置数组,将分别为每个对象分别调用 [[yii\di\Container::set()|set()]] +或 [[yii\di\Container::setSingleton()|setSingleton()]] 方法。 配置数组格式为: - - `key`: class name, interface name or alias name. The key will be passed to the - [[yii\di\Container::set()|set()]] method as a first argument `$class`. - - `value`: the definition associated with `$class`. Possible values are described in [[yii\di\Container::set()|set()]] - documentation for the `$definition` parameter. Will be passed to the [[set()]] method as - the second argument `$definition`. + - `key`:类名称,接口名称或别名。 该 key 将作为第一个参数 + `$class` 传递给 [[yii\di\Container::set()|set()]] 方法。 + - `value`:与 `$class` 关联的定义。`$definition` 参数的值可能在 [[yii\di\Container::set()|set()]] + 文档中描述。`$definition` 将作为第二个参数传递给 [[set()]] + 方法。 例如,让我们配置我们的容器以遵循上述要求: @@ -428,32 +428,32 @@ $container->setDefinitions([ ]); $reader = $container->get('app\storage\DocumentsReader); -// Will create DocumentReader object with its dependencies as described in the config +// 将按照配置中的描述创建 DocumentReader 对象及其依赖关系 ``` -> Tip: Container may be configured in declarative style using application configuration since version 2.0.11. -Check out the [Application Configurations](concept-configurations.md#application-configurations) subsection of -the [Configurations](concept-configurations.md) guide article. +> Tip: 自 2.0.11 版以后,可以使用应用程序配置以声明式风格配置容器。 +查看[配置](concept-configurations.md)指南文章的 +[应用程序配置](concept-configurations.md#application-configurations)小节。 -Everything works, but in case we need to create `DocumentWriter` class, -we shall copy-paste the line that creates `FileStorage` object, that is not the smartest way, obviously. +一切正常,但如果我们需要创建 `Document Writer` 类, +我们将复制粘贴创建 `FileStorage` 对象的行,这显然不是最聪明的方式。 -As described in the [Resolving Dependencies](#resolving-dependencies) subsection, [[yii\di\Container::set()|set()]] -and [[yii\di\Container::setSingleton()|setSingleton()]] can optionally take dependency's constructor parameters as -a third argument. To set the constructor parameters, you may use the following configuration array format: +如 [解决依赖关系](#resolving-dependencies) 子节中所述,[[yii\di\Container::set()|set()]] +和 [[yii\di\Container::setSingleton()|setSingleton()]] 可以选择将依赖项的构造函数参数作为第三个参数。 +要设置构造函数参数,可以使用以下配置数组格式: - - `key`: class name, interface name or alias name. The key will be passed to the - [[yii\di\Container::set()|set()]] method as a first argument `$class`. - - `value`: array of two elements. The first element will be passed to the [[yii\di\Container::set()|set()]] method as the - second argument `$definition`, the second one — as `$params`. + - `key`:类名称,接口名称或别名。该 key 将作为第一个参数 + `$class` 传递给 [[yii\di\Container::set()|set()]] 方法。 + - `value`:两个元素的数组。第一个元素将传递给 [[yii\di\Container::set()|set()]] 方法 + 作为第二个参数 `$definition`,第二个元素为 `$params`。 让我们来修改我们的例子: ```php $container->setDefinitions([ - 'tempFileStorage' => [ // we've created an alias for convenience + 'tempFileStorage' => [ // 我们为了方便创建了一个别名 ['class' => 'app\storage\FileStorage'], - ['/var/tempfiles'] // could be extracted from some config files + ['/var/tempfiles'] // 可以从一些配置文件中获取 ], 'app\storage\DocumentsReader' => [ ['class' => 'app\storage\DocumentsReader'], @@ -466,7 +466,7 @@ $container->setDefinitions([ ]); $reader = $container->get('app\storage\DocumentsReader); -// Will behave exactly the same as in the previous example. +// 将与前面示例中的行为完全相同。 ``` 你可能会注意到 `Instance::of('tempFileStorage')` 符号。这意味着,[[yii\di\Container|Container]] @@ -507,8 +507,8 @@ $container->setDefinitions([ $reader = $container->get('app\storage\DocumentsReader'); ``` -什么时候注册依赖关系 ------------------------------ +什么时候注册依赖关系(When to Register Dependencies) +------------------------------------------------ 由于依赖关系在创建新对象时需要解决,因此它们的注册应该尽早完成。 如下是推荐的实践: @@ -520,8 +520,8 @@ $reader = $container->get('app\storage\DocumentsReader'); 你可以将依赖关系注册到扩展的引导类中。 -总结 -------- +总结(Summary) +------------- 依赖注入和[服务定位器](concept-service-locator.md)都是流行的设计模式, 它们使你可以用充分解耦且更利于测试的风格构建软件。 diff --git a/docs/guide-zh-CN/concept-events.md b/docs/guide-zh-CN/concept-events.md index c96846e..a80967d 100644 --- a/docs/guide-zh-CN/concept-events.md +++ b/docs/guide-zh-CN/concept-events.md @@ -1,5 +1,5 @@ -事件 -====== +事件(Events) +============ 事件可以将自定义代码“注入”到现有代码中的特定执行点。 附加自定义代码到某个事件,当这个事件被触发时,这些代码就会自动执行。 @@ -10,8 +10,8 @@ Yii 引入了名为 [[yii\base\Component]] 的基类以支持事件。 如果一个类需要触发事件就应该继承 [[yii\base\Component]] 或其子类。 -事件处理器 --------------- +事件处理器(Event Handlers) +------------------------- 事件处理器是一个[PHP 回调函数](http://www.php.net/manual/en/language.types.callable.php), 当它所附加到的事件被触发时它就会执行。可以使用以下回调函数之一: @@ -36,8 +36,8 @@ function ($event) { - [[yii\base\Event::data|custom data]]:附加事件处理器时传入的数据,默认为空,后文详述 -附加事件处理器 ----------------- +附加事件处理器(Attaching Event Handlers) +-------------------------------------- 调用 [[yii\base\Component::on()]] 方法来附加处理器到事件上。如: @@ -76,8 +76,8 @@ function function_name($event) { } ``` -事件处理器顺序 ------------------ +事件处理器顺序(Event Handler Order) +--------------------------------- 可以附加一个或多个处理器到一个事件。当事件被触发,已附加的处理器将按附加次序依次调用。 如果某个处理器需要停止其后的处理器调用,可以设置 `$event` 参数的 [[yii\base\Event::handled]] 属性为真, @@ -99,10 +99,10 @@ $foo->on(Foo::EVENT_HELLO, function ($event) { }, $data, false); ``` -触发事件 ----------- +触发事件(Triggering Events) +-------------------------- -事件通过调用 [[yii\base\Component::trigger()]] 方法触发,此方法须传递**事件名**, +事件通过调用 [[yii\base\Component::trigger()]] 方法触发,此方法须传递*事件名*, 还可以传递一个事件对象,用来传递参数到事件处理器。如: ```php @@ -164,8 +164,8 @@ class Mailer extends Component 它将调用所有附加到命名事件(trigger 方法第一个参数)的事件处理器。 -移除事件处理器 ---------------- +移除事件处理器(Detaching Event Handlers) +-------------------------------------- 从事件移除处理器,调用 [[yii\base\Component::off()]] 方法。如: @@ -194,13 +194,13 @@ $foo->off(Foo::EVENT_HELLO); ``` -类级别的事件处理器 -------------------- +类级别的事件处理器(Class-Level Event Handlers) +------------------------------------------- -以上部分,我们叙述了在**实例级别**如何附加处理器到事件。 -有时想要一个类的所有实例而不是一个指定的实例都响应一个被触发的事件, +以上部分,我们叙述了在*实例级别*如何附加处理器到事件。 +有时想要一个类的*所有*实例而不是一个指定的实例都响应一个被触发的事件, 并不是一个个附加事件处理器到每个实例, -而是通过调用静态方法 [[yii\base\Event::on()]] 在**类级别**附加处理器。 +而是通过调用静态方法 [[yii\base\Event::on()]] 在*类级别*附加处理器。 例如,[活动记录](db-active-record.md)对象要在每次往数据库新增一条新记录时触发一个 [[yii\db\BaseActiveRecord::EVENT_AFTER_INSERT|EVENT_AFTER_INSERT]] 事件。 @@ -222,7 +222,7 @@ Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function 当对象触发事件时,它首先调用实例级别的处理器,然后才会调用类级别处理器。 -可调用静态方法[[yii\base\Event::trigger()]]来触发一个**类级别**事件。 +可调用静态方法[[yii\base\Event::trigger()]]来触发一个*类级别*事件。 类级别事件不与特定对象相关联。因此,它只会引起类级别事件处理器的调用。 如: @@ -252,8 +252,8 @@ Event::off(Foo::className(), Foo::EVENT_HELLO); ``` -使用接口事件 -------------- +使用接口事件(Events using interfaces) +----------------------------------- 有更多的抽象方式来处理事件。你可以为特殊的事件创建一个独立的接口, 然后在你需要的类中实现它。 @@ -328,10 +328,10 @@ Event::off('app\interfaces\DanceEventInterface', DanceEventInterface::EVENT_DANC ``` -全局事件 -------------- +全局事件(Global Events) +---------------------- -所谓**全局事件**实际上是一个基于以上叙述的事件机制的戏法。它需要一个全局可访问的单例, +所谓*全局事件*实际上是一个基于以上叙述的事件机制的戏法。它需要一个全局可访问的单例, 如[应用](structure-applications.md)实例。 事件触发者不调用其自身的 `trigger()` 方法,而是调用单例的 `trigger()` 方法来触发全局事件。 @@ -356,3 +356,73 @@ Yii::$app->trigger('bar', new Event(['sender' => new Foo])); 然而,因为全局事件的命名空间由各方共享,应合理命名全局事件, 如引入一些命名空间(例:"frontend.mail.sent", "backend.mail.sent")。 + +通配符事件(Wildcard Events) +-------------------------- + +自 2.0.14 以来,您可以为多个匹配通配符模式的事件设置事件处理程序。 +例如: + +```php +use Yii; + +$foo = new Foo(); + +$foo->on('foo.event.*', function ($event) { + // 触发任何事件,该名称以 'foo.event.' 开头 + Yii::debug('trigger event: ' . $event->name); +}); +``` + +通配符模式也可以用于类级别的事件。 例如: + +```php +use yii\base\Event; +use Yii; + +Event::on('app\models\*', 'before*', function ($event) { + // 触发命名空间 'app\models' 中的任何类的任何事件,名称以 'before' 开头。 + Yii::debug('trigger event: ' . $event->name . ' for class: ' . get_class($event->sender)); +}); +``` + +这允许您使用以下代码通过单个处理程序捕获所有应用程序事件: + +```php +use yii\base\Event; +use Yii; + +Event::on('*', '*', function ($event) { + // 触发任何类的任何事件 + Yii::debug('trigger event: ' . $event->name); +}); +``` + +> Note: 事件处理程序设置的使用通配符可能会降低应用程序的性能。 + 如果可能,最好避免。 + +为了移除由通配符模式指定的事件处理程序,您应该在 +[[yii\base\Component::off()]] 或 [[yii\base\Event::off()]] 调用中重复相同的模式。 +请记住,在移除事件处理程序期间传递通配符将移除为此通配符指定的处理程序, +而为常规事件名称附加的处理程序将保留,即使它们与模式匹配。 例如: + +```php +use Yii; + +$foo = new Foo(); + +// 附加常规处理 +$foo->on('event.hello', function ($event) { + echo 'direct-handler' +}); + +// 附加通配符处理程序 +$foo->on('*', function ($event) { + echo 'wildcard-handler' +}); + +// 仅移除通配符处理程序! +$foo->off('*'); + +$foo->trigger('event.hello'); // 输出:'direct-handler' +``` diff --git a/docs/guide-zh-CN/concept-properties.md b/docs/guide-zh-CN/concept-properties.md index 6695aec..0998981 100644 --- a/docs/guide-zh-CN/concept-properties.md +++ b/docs/guide-zh-CN/concept-properties.md @@ -1,7 +1,7 @@ -属性(Property) -========== +属性(Properties) +================ -在 PHP 中,类的成员变量也被称为**属性(properties)**。它们是类定义的一部分, +在 PHP 中,类的成员变量也被称为*属性*。它们是类定义的一部分, 用来表现一个实例的状态(也就是区分类的不同实例)。 在具体实践中,常常会想用一个稍微特殊些的方法实现属性的读写。 例如,如果有需求每次都要对 `label` 属性执行 trim 操作, @@ -17,7 +17,7 @@ $object->label = trim($label); 这种实践显然需要尽可能避免。 为解决该问题,Yii 引入了一个名为 [[yii\base\Object]] 的基类, -它支持基于类内的 **getter** 和 **setter**(读取器和设定器)方法来定义属性。 +它支持基于类内的 *getter* 和 *setter*(读取器和设定器)方法来定义属性。 如果某类需要支持这个特性,只需要继承 [[yii\base\Object]] 或其子类即可。 > Info: 几乎每个 Yii 框架的核心类都继承自 [[yii\base\Object]] 或其子类。 @@ -63,20 +63,20 @@ $label = $object->label; $object->label = 'abc'; ``` -只定义了 getter 没有 setter 的属性是**只读属性**。 +只定义了 getter 没有 setter 的属性是*只读属性*。 尝试赋值给这样的属性将导致 [[yii\base\InvalidCallException|InvalidCallException]] (无效调用)异常。 -类似的,只有 setter 方法而没有 getter 方法定义的属性是**只写属性**, +类似的,只有 setter 方法而没有 getter 方法定义的属性是*只写属性*, 尝试读取这种属性也会触发异常。使用只写属性的情况几乎没有。 通过 getter 和 setter 定义的属性也有一些特殊规则和限制: -* 这类属性的名字是**不区分大小写**的。如,`$object->label` 和 `$object->Label` 是同一个属性。 +* 这类属性的名字是*不区分大小写*的。如,`$object->label` 和 `$object->Label` 是同一个属性。 因为 PHP 方法名是不区分大小写的。 * 如果此类属性名和类成员变量相同,以后者为准。例如, 假设以上 `Foo` 类有个 `label` 成员变量,然后给 `$object->label = 'abc'` 赋值, 将赋给成员变量而不是 setter `setLabel()` 方法。 * 这类属性不支持可见性(访问限制)。定义属性的 getter 和 setter 方法是 public、protected 还是 private 对属性的可见性没有任何影响。 -* 这类属性的 getter 和 setter 方法只能定义为**非静态**的,若定义为静态方法(static)则不会以相同方式处理。 +* 这类属性的 getter 和 setter 方法只能定义为*非静态*的,若定义为静态方法(static)则不会以相同方式处理。 * 对不确定有无魔术方法(getter 或 setter)的属性正常调用 `property_exists()` 将不会生效。你应该分别调用 [[yii\base\BaseObject::canGetProperty()|canGetProperty()]] 或 [[yii\base\BaseObject::canSetProperty()|canSetProperty()]] 。 @@ -84,4 +84,3 @@ $object->label = 'abc'; 现在我们只需在 setter `setLabel()` 方法内调用一次。 如果 label 首字母变成大写的新要求来了,我们只需要修改`setLabel()` 方法, 而无须接触任何其它代码。 - diff --git a/docs/guide-zh-CN/concept-service-locator.md b/docs/guide-zh-CN/concept-service-locator.md index ffd9cb4..b8da47d 100644 --- a/docs/guide-zh-CN/concept-service-locator.md +++ b/docs/guide-zh-CN/concept-service-locator.md @@ -1,5 +1,5 @@ -服务定位器 -=============== +服务定位器(Service Locator) +========================== 服务定位器是一个了解如何提供各种应用所需的服务(或组件)的对象。在服务定位器中, 每个组件都只有一个单独的实例,并通过ID 唯一地标识。 @@ -7,8 +7,8 @@ 在 Yii 中,服务定位器是 [[yii\di\ServiceLocator]] 或其子类的一个实例。 -最常用的服务定位器是**application(应用)**对象,可以通过 `\Yii::$app` 访问。 -它所提供的服务被称为**application components(应用组件)**, +最常用的服务定位器是*application(应用)*对象,可以通过 `\Yii::$app` 访问。 +它所提供的服务被称为*application components(应用组件)*, 比如:`request`、`response`、`urlManager` 组件。可以通过服务定位器所提供的功能, 非常容易地配置这些组件,或甚至是用你自己的实现替换掉他们。 @@ -119,14 +119,13 @@ return [ 当您使用如上所示的静态方法来表示构建复杂逻辑的第三方对象时, 您的组件用户只需要调用静态方法来配置组件。 -## Tree traversal +## 遍历树(Tree traversal) -Modules allow arbitrary nesting; a Yii application is essentially a tree of modules. -Since each of these modules is a service locator it makes sense for children to have access to their parent. -This allows modules to use `$this->get('db')` instead of referencing the root service locator `Yii::$app->get('db')`. -Added benefit is the option for a developer to override configuration in a module. +模块允许任意嵌套; Yii 应用程序本质上是一个模块树。 +由于这些模块中的每一个都是服务定位器,所以子模块有权限访问其父模块。 +这允许模块使用 `$this->get('db')` 而不是引用根服务定位器 `Yii::$app->get('db')`。 +增加的好处是开发人员可以覆盖模块中的配置。 -Any request for a service to be retrieved from a module will be passed on to its parent in case the module is not able to satisfy it. - -Note that configuration from components in a module is never merged with configuration from a component in a parent module. The Service Locator pattern allows us to define named services but one cannot assume servicees with the same name use the same configuration parameters. +如果模块无法满足要求,则从模块中检索服务的请求将被传递给它的父模块。 +请注意,模块中组件的配置决不会与来自父模块中组件的配置合并。Service Locator 模式允许我们定义命名服务,但不能假定具有相同名称的服务使用相同的配置参数。 diff --git a/docs/guide-zh-CN/db-active-record.md b/docs/guide-zh-CN/db-active-record.md index dffa5de..5aaaa50 100644 --- a/docs/guide-zh-CN/db-active-record.md +++ b/docs/guide-zh-CN/db-active-record.md @@ -1,14 +1,14 @@ -活动记录 -============= +活动记录(Active Record) +====================== -[Active Record](http://zh.wikipedia.org/wiki/Active_Record) (活动记录,以下简称 AR)提供了一个面向对象的接口, -用以访问和操作数据库中的数据。一个 AR 类关联一张数据表, -每个 AR 对象对应表中的一行,对象的属性(即 AR 的特性Attribute)映射到数据行的对应列。 -即一条活动记录(AR 对象)对应数据表的一行,AR 对象的属性则映射该行的相应列。 -您可以直接以面向对象的方式来操纵数据表中的数据, +[Active Record](http://zh.wikipedia.org/wiki/Active_Record) 提供了一个面向对象的接口, +用以访问和操作数据库中的数据。Active Record 类与数据库表关联, +Active Record 实例对应于该表的一行, +Active Record 实例的*属性*表示该行中特定列的值。 +您可以访问 Active Record 属性并调用 Active Record 方法来访问和操作存储在数据库表中的数据, +而不用编写原始 SQL 语句。 - -例如,假定 `Customer` AR 类关联着 `customer` 表, +例如,假定 `Customer` Active Record 类关联着 `customer` 表, 且该类的 `name` 属性代表 `customer` 表的 `name` 列。 你可以写以下代码来哉 `customer` 表里插入一行新的记录: @@ -27,45 +27,45 @@ $db->createCommand('INSERT INTO `customer` (`name`) VALUES (:name)', [ ])->execute(); ``` -下面是所有目前被 Yii 的 AR 功能所支持的数据库列表: +Yii 为以下关系数据库提供 Active Record 支持: -* MySQL 4.1 及以上: 通过 [[yii\db\ActiveRecord]] 支持 +* MySQL 4.1 及以上:通过 [[yii\db\ActiveRecord]] 支持 * PostgreSQL 7.3 及以上:通过 [[yii\db\ActiveRecord]] 支持 -* SQLite 2 and 3: 通过 [[yii\db\ActiveRecord]] 支持 +* SQLite 2 and 3:通过 [[yii\db\ActiveRecord]] 支持 * Microsoft SQL Server 2008 及以上:通过 [[yii\db\ActiveRecord]] 支持 -* Oracle: 通过 [[yii\db\ActiveRecord]] 支持 +* Oracle:通过 [[yii\db\ActiveRecord]] 支持 * CUBRID 9.3 及以上:通过 [[yii\db\ActiveRecord]] 支持 (提示, 由于 CUBRID PDO 扩展的 [bug](http://jira.cubrid.org/browse/APIS-658), 给变量加引用将不起作用,所以你得使用 CUBRID 9.3 客户端及服务端。 -* Sphinx: 通过 [[yii\sphinx\ActiveRecord]] 支持, 依赖 `yii2-sphinx` 扩展 -* ElasticSearch: 通过 [[yii\elasticsearch\ActiveRecord]] 支持, 依赖 `yii2-elasticsearch` 扩展 +* Sphinx:通过 [[yii\sphinx\ActiveRecord]] 支持, 依赖 `yii2-sphinx` 扩展 +* ElasticSearch:通过 [[yii\elasticsearch\ActiveRecord]] 支持, 依赖 `yii2-elasticsearch` 扩展 -此外,Yii 的 AR 功能还支持以下 NoSQL 数据库: +此外,Yii 的 Active Record 功能还支持以下 NoSQL 数据库: * Redis 2.6.12 及以上: 通过 [[yii\redis\ActiveRecord]] 支持, 依赖 `yii2-redis` 扩展 * MongoDB 1.3.0 及以上: 通过 [[yii\mongodb\ActiveRecord]] 支持, 依赖 `yii2-mongodb` 扩展 -在本教程中,我们会主要描述对关系型数据库的 AR 用法。 -然而,绝大多数的内容在 NoSQL 的 AR 里同样适用。 +在本教程中,我们会主要描述对关系型数据库的 Active Record 用法。 +然而,绝大多数的内容在 NoSQL 的 Active Record 里同样适用。 -## 声明 AR 类 +## 声明 Active Record 类(Declaring Active Record Classes) -要想声明一个 AR 类,你需要定义几个类 继承 [[yii\db\ActiveRecord]]. +要想声明一个 Active Record 类,你需要声明该类继承 [[yii\db\ActiveRecord]]。 -### 设置表的名称 +### 设置表的名称(Setting a table name) -默认的,每个 AR 类关联各自的数据库表。 +默认的,每个 Active Record 类关联各自的数据库表。 经过 [[yii\helpers\Inflector::camel2id()]] 处理,[[yii\db\ActiveRecord::tableName()|tableName()]] 方法默认返回的表名称是通过类名转换来得。 如果这个默认名称不正确,你得重写这个方法。 -此外, [[yii\db\Connection::$tablePrefix|tablePrefix]] 表前缀也会起作用。 例如, 如果 - [[yii\db\Connection::$tablePrefix|tablePrefix]] 表前缀是 `tbl_`, `Customer` 的类名将转换成 `tbl_customer` 表名, `OrderItem` 转换成 `tbl_order_item`. +此外,[[yii\db\Connection::$tablePrefix|tablePrefix]] 表前缀也会起作用。例如,如果 +[[yii\db\Connection::$tablePrefix|tablePrefix]] 表前缀是 `tbl_`,`Customer` 的类名将转换成 `tbl_customer` 表名,`OrderItem` 转换成 `tbl_order_item`。 如果你定义的表名是 `{{%TableName}}`, 百分比字符 `%` 会被替换成表前缀。 -例如, `{{%post}}` 会变成 `{{tbl_post}}`。 表名两边的括号会被 [SQL 查询引用](db-dao.md#quoting-table-and-column-names) 处理。 +例如, `{{%post}}` 会变成 `{{tbl_post}}`。表名两边的括号会被 [SQL 查询引用](db-dao.md#quoting-table-and-column-names) 处理。 -下面的例子中,我们给 `customer` 数据库表定义叫 `Customer` 的 AR 类。 +下面的例子中,我们给 `customer` 数据库表定义叫 `Customer` 的 Active Record 类。 ```php namespace app\models; @@ -78,7 +78,7 @@ class Customer extends ActiveRecord const STATUS_ACTIVE = 1; /** - * @return string AR 类关联的数据库表名称 + * @return string Active Record 类关联的数据库表名称 */ public static function tableName() { @@ -87,19 +87,19 @@ class Customer extends ActiveRecord } ``` -### 将 AR 称为模型吧 -AR 实例称为 [模型](structure-models.md)。因此, 我们通常将 AR 类 +### 将 Active Record 称为模型(Active records are called "models") +Active Record 实例称为[模型](structure-models.md)。因此, 我们通常将 Active Record 类 放在 `app\models` 命名空间下(或者其他保存模型的命名空间)。 -因为 AR [[yii\db\ActiveRecord]] 继承了模型 [[yii\base\Model]], 它就拥有所有 [模型](structure-models.md) 特性, -比如说属性(attributes),检验规则(rules),数据序列化,等等。 +因为 [[yii\db\ActiveRecord]] 继承了模型 [[yii\base\Model]], 它就拥有所有[模型](structure-models.md)特性, +比如说属性(attributes),验证规则(rules),数据序列化(data serialization),等等。 -## 建立数据库连接 +## 建立数据库连接(Connecting to Databases) -活动记录 AR 默认使用 `db` [组件](structure-application-components.md) +活动记录 Active Record 默认使用 `db` [组件](structure-application-components.md) 作为连接器 [[yii\db\Connection|DB connection]] 访问和操作数据库数据。 -基于 [数据库访问](db-dao.md) 中的解释,你可以在系统配置中 +基于[数据库访问](db-dao.md)中的解释,你可以在系统配置中 这样配置 `db` 组件。 ```php @@ -132,20 +132,20 @@ class Customer extends ActiveRecord ``` -## 查询数据 +## 查询数据(Querying Data) -定义 AR 类后,你可以从相应的数据库表中查询数据。 +定义 Active Record 类后,你可以从相应的数据库表中查询数据。 查询过程大致如下三个步骤: 1. 通过 [[yii\db\ActiveRecord::find()]] 方法创建一个新的查询生成器对象; -2. 使用 [查询生成器的构建方法](db-query-builder.md#building-queries) 来构建你的查询; -3. 调用 [查询生成器的查询方法](db-query-builder.md#query-methods) 来取出数据到 AR 实例中。 +2. 使用[查询生成器的构建方法](db-query-builder.md#building-queries)来构建你的查询; +3. 调用[查询生成器的查询方法](db-query-builder.md#query-methods)来取出数据到 Active Record 实例中。 -你瞅瞅, 是不是跟 [查询生成器](db-query-builder.md) 的步骤差不多。 +正如你看到的,是不是跟[查询生成器](db-query-builder.md)的步骤差不多。 唯一有区别的地方在于你用 [[yii\db\ActiveRecord::find()]] 去获得一个新的查询生成器对象,这个对象是 [[yii\db\ActiveQuery]], 而不是使用 `new` 操作符创建一个查询生成器对象。 -下面是一些栗子,介绍如何使用 AR 查询数据: +下面是一些例子,介绍如何使用 Active Query 查询数据: ```php // 返回 ID 为 123 的客户: @@ -177,22 +177,22 @@ $customers = Customer::find() 上述代码中,`$customer` 是个 `Customer` 对象,而 `$customers` 是个以 `Customer` 对象为元素的数组。 它们两都是以 `customer` 表中取回的数据结果集填充的。 -> Tip: 由于 [[yii\db\ActiveQuery]] 继承 [[yii\db\Query]],你可以使用 [查询生成器](db-query-builder.md) 章节里所描述的所有查询方法。 - +> Tip: 由于 [[yii\db\ActiveQuery]] 继承 [[yii\db\Query]], + 你可以使用 [Query Builder](db-query-builder.md) 章节里所描述的*所有*查询方法。 根据主键获取数据行是比较常见的操作,所以 Yii 提供了两个快捷方法: -- [[yii\db\ActiveRecord::findOne()]]: 返回一个 AR 实例,填充于查询结果的第一行数据。 -- [[yii\db\ActiveRecord::findAll()]]:返回一个 AR 实例的数据,填充于查询结果的全部数据。 +- [[yii\db\ActiveRecord::findOne()]]:返回一个 Active Record 实例,填充于查询结果的第一行数据。 +- [[yii\db\ActiveRecord::findAll()]]:返回一个 Active Record 实例的数据,填充于查询结果的全部数据。 这两个方法的传参格式如下: - 标量值:这个值会当作主键去查询。 - Yii 会通过读取数据库模式信息来识别主键列。 + Yii 会通过读取数据库模式信息来识别主键列。 - 标量值的数组:这数组里的值都当作要查询的主键的值。 - 关联数组:键值是表的列名,元素值是相应的要查询的条件值。 -可以到 [哈希格式](db-query-builder.md#hash-format) 查看更多信息。 + 可以到 [哈希格式](db-query-builder.md#hash-format) 查看更多信息。 如下代码描述如何使用这些方法: @@ -219,11 +219,30 @@ $customers = Customer::findAll([ ]); ``` +> 注:如果你需要将用户输入传递给这些方法,请确保输入值是标量或者是 +> 数组条件,确保数组结构不能被外部所改变: +> +> ```php +> // yii\web\Controller 确保了 $id 是标量 +> public function actionView($id) +> { +> $model = Post::findOne($id); +> // ... +> } +> +> // 明确了指定要搜索的列,在此处传递标量或数组将始终只是查找出单个记录而已 +> $model = Post::findOne(['id' => Yii::$app->request->get('id')]); +> +> // 不要使用下面的代码!可以注入一个数组条件来匹配任意列的值! +> $model = Post::findOne(Yii::$app->request->get('id')); +> ``` + + > Tip: [[yii\db\ActiveRecord::findOne()]] 和 [[yii\db\ActiveQuery::one()]] 都不会添加 `LIMIT 1` 到 生成的 SQL 语句中。如果你的查询会返回很多行的数据, 你明确的应该加上 `limit(1)` 来提高性能,比如 `Customer::find()->limit(1)->one()`。 -除了使用查询生成器的方法之外,你还可以书写原生的 SQL 语句来查询数据,并填充结果集到 AR 对象中。 +除了使用查询生成器的方法之外,你还可以书写原生的 SQL 语句来查询数据,并填充结果集到 Active Record 对象中。 通过使用 [[yii\db\ActiveRecord::findBySql()]] 方法: ```php @@ -236,11 +255,11 @@ $customers = Customer::findBySql($sql, [':status' => Customer::STATUS_INACTIVE]) 多余的查询方法都会被忽略。 -## 访问数据 +## 访问数据(Accessing Data) -如上所述,从数据库返回的数据被填充到 AR 实例中, -查询结果的每一行对应于单个 AR 实例。 -您可以通过 AR 实例的属性来访问列值,例如, +如上所述,从数据库返回的数据被填充到 Active Record 实例中, +查询结果的每一行对应于单个 Active Record 实例。 +您可以通过 Active Record 实例的属性来访问列值,例如, ```php // "id" 和 "email" 是 "customer" 表中的列名 @@ -249,23 +268,23 @@ $id = $customer->id; $email = $customer->email; ``` -> Tip: AR 的属性以区分大小写的方式为相关联的表列命名的。 - Yii 会自动为关联表的每一列定义 AR 中的一个属性。 +> Tip: Active Record 的属性以区分大小写的方式为相关联的表列命名的。 + Yii 会自动为关联表的每一列定义 Active Record 中的一个属性。 您不应该重新声明任何属性。 -由于 AR 的属性以表的列名命名,可能你会发现你正在编写像这样的 PHP 代码 : - `$ customer-> first_name`,如果你的表的列名是使用下划线分隔的,那么属性名中的单词 +由于 Active Record 的属性以表的列名命名,可能你会发现你正在编写像这样的 PHP 代码: +`$ customer-> first_name`,如果你的表的列名是使用下划线分隔的,那么属性名中的单词 以这种方式命名。 如果您担心代码风格一致性的问题,那么你应当重命名相应的表列名 (例如使用骆驼拼写法)。 -### 数据转换 +### 数据转换(Data Transformation) 常常遇到,要输入或显示的数据是一种格式,而要将其存储在数据库中是另一种格式。 例如,在数据库中,您将客户的生日存储为 UNIX 时间戳(虽然这不是一个很好的设计), 而在大多数情况下,你想以字符串 `'YYYY/MM/DD'` 的格式处理生日数据。 为了实现这一目标,您可以在 `Customer` 中定义 *数据转换* 方法 -定义 AR 类如下: +定义 Active Record 类如下: ```php class Customer extends ActiveRecord @@ -285,16 +304,16 @@ class Customer extends ActiveRecord ``` 现在你的 PHP 代码中,你可以访问 `$ customer-> birthdayText`, -来以 `'YYYY/MM/DD'` 的格式输入和显示客户生日,而不是访问`$ customer-> birthday`。 +来以 `'YYYY/MM/DD'` 的格式输入和显示客户生日,而不是访问 `$ customer-> birthday`。 > Tip: 上述示例显示了以不同格式转换数据的通用方法。如果你正在使用 > 日期值,您可以使用 [DateValidator](tutorial-core-validators.md#date) 和 [[yii\jui\DatePicker|DatePicker]] 来操作, > 这将更易用,更强大。 -### 以数组形式获取数据 +### 以数组形式获取数据(Retrieving Data in Arrays) -通过 AR 对象获取数据十分方便灵活,与此同时,当你需要返回大量的数据的时候, +通过 Active Record 对象获取数据十分方便灵活,与此同时,当你需要返回大量的数据的时候, 这样的做法并不令人满意,因为这将导致大量内存占用。在这种情况下,您可以 在查询方法前调用 [[yii\db\ActiveQuery::asArray()|asArray()]] 方法,来获取 PHP 数组形式的结果: @@ -307,16 +326,16 @@ $customers = Customer::find() ``` > Tip: 虽然这种方法可以节省内存并提高性能,但它更靠近较低的 DB 抽象层 - 你将失去大部分的 AR 提供的功能。 一个非常重要的区别在于列值的数据类型。 - 当您在 AR 实例中返回数据时,列值将根据实际列类型,自动类型转换; + 你将失去大部分的 Active Record 提供的功能。 一个非常重要的区别在于列值的数据类型。 + 当您在 Active Record 实例中返回数据时,列值将根据实际列类型,自动类型转换; 然而,当您以数组返回数据时,列值将为 字符串(因为它们是没有处理过的 PDO 的结果),不管它们的实际列是什么类型。 -### 批量获取数据 +### 批量获取数据(Retrieving Data in Batches) 在 [查询生成器](db-query-builder.md) 中,我们已经解释说可以使用 *批处理查询* 来最小化你的内存使用, -每当从数据库查询大量数据。你可以在 AR 中使用同样的技巧。例如, +每当从数据库查询大量数据。你可以在 Active Record 中使用同样的技巧。例如, ```php // 每次获取 10 条客户数据 @@ -336,15 +355,15 @@ foreach (Customer::find()->with('orders')->each() as $customer) { ``` -## 保存数据 +## 保存数据(Saving Data) -使用 AR (活动记录),您可以通过以下步骤轻松地将数据保存到数据库: +使用 Active Record,您可以通过以下步骤轻松地将数据保存到数据库: -1. 准备一个 AR 实例 -2. 将新值赋给 AR 的属性 +1. 准备一个 Active Record 实例 +2. 将新值赋给 Active Record 的属性 3. 调用 [[yii\db\ActiveRecord::save()]] 保存数据到数据库中。 -举个栗子, +例如, ```php // 插入新记录 @@ -359,12 +378,12 @@ $customer->email = 'james@newexample.com'; $customer->save(); ``` -[[yii\db\ActiveRecord::save()|save()]] 方法可能插入或者更新表的记录,这取决于 AR 实例的状态。 +[[yii\db\ActiveRecord::save()|save()]] 方法可能插入或者更新表的记录,这取决于 Active Record 实例的状态。 如果实例通过 `new` 操作符实例化,调用 [[yii\db\ActiveRecord::save()|save()]] 方法将插入新记录; 如果实例是一个查询方法的结果,调用 [[yii\db\ActiveRecord::save()|save()]] 方法 将更新这个实例对应的表记录行。 -你可以通过检查 AR 实例的 [[yii\db\ActiveRecord::isNewRecord|isNewRecord]] 属性值来区分这两个状态。 +你可以通过检查 Active Record 实例的 [[yii\db\ActiveRecord::isNewRecord|isNewRecord]] 属性值来区分这两个状态。 此属性也被使用在 [[yii\db\ActiveRecord::save()|save()]] 方法内部, 代码如下: @@ -383,7 +402,7 @@ public function save($runValidation = true, $attributeNames = null) 方法来插入或更新一条记录。 -### 数据验证 +### 数据验证(Data Validation) 因为 [[yii\db\ActiveRecord]] 继承于 [[yii\base\Model]],它共享相同的 [输入验证](input-validation.md) 功能。 你可以通过重写 [[yii\db\ActiveRecord::rules()|rules()]] 方法声明验证规则并执行, @@ -397,10 +416,10 @@ public function save($runValidation = true, $attributeNames = null) 你可以调用 `save(false)` 来跳过验证过程。 -### 块赋值 +### 块赋值(Massive Assignment) -和普通的 [models](structure-models.md) 一样,你亦可以享受 AR 实例的 [块赋值](structure-models.md#massive-assignment) 特性。 -使用此功能,您可以在单个 PHP 语句中,给 AR 实例的多个属性批量赋值, +和普通的 [models](structure-models.md) 一样,你亦可以享受 Active Record 实例的 [块赋值](structure-models.md#massive-assignment) 特性。 +使用此功能,您可以在单个 PHP 语句中,给 Active Record 实例的多个属性批量赋值, 如下所示。 记住,只有 [安全属性](structure-models.md#safe-attributes) 才可以批量赋值。 ```php @@ -416,7 +435,7 @@ $customer->save(); ``` -### 更新计数 +### 更新计数(Updating Counters) 在数据库表中增加或减少一个字段的值是个常见的任务。我们将这些列称为“计数列”。 您可以使用 [[yii\db\ActiveRecord::updateCounters()|updateCounters()]] 更新一个或多个计数列。 @@ -429,39 +448,39 @@ $post = Post::findOne(100); $post->updateCounters(['view_count' => 1]); ``` -> 注:如果你使用 [[yii\db\ActiveRecord::save()]] 更新一个计数列,你最终将得到错误的结果, +> Note: 如果你使用 [[yii\db\ActiveRecord::save()]] 更新一个计数列,你最终将得到错误的结果, 因为可能发生这种情况,多个请求间并发读写同一个计数列。 -### 脏属性 +### 脏属性(Dirty Attributes) -当您调用 [[yii\db\ActiveRecord::save()|save()]] 保存 AR 实例时,只有 *脏属性* +当您调用 [[yii\db\ActiveRecord::save()|save()]] 保存 Active Record 实例时,只有 *脏属性* 被保存。如果一个属性的值已被修改,则会被认为是 *脏*,因为它是从 DB 加载出来的或者 -刚刚保存到 DB 。请注意,无论如何 AR 都会执行数据验证 +刚刚保存到 DB 。请注意,无论如何 Active Record 都会执行数据验证 不管有没有脏属性。 -AR 自动维护脏属性列表。 它保存所有属性的旧值, +Active Record 自动维护脏属性列表。 它保存所有属性的旧值, 并其与最新的属性值进行比较,就是酱紫个道理。你可以调用 [[yii\db\ActiveRecord::getDirtyAttributes()]] 获取当前的脏属性。你也可以调用 [[yii\db\ActiveRecord::getDirtyAttributes()]] 将属性显式标记为脏。 如果你有需要获取属性原先的值,你可以调用 -[[yii\db\ActiveRecord::getOldAttributes()|getOldAttributes()]] 或者 [[yii\db\ActiveRecord::getOldAttribute()|getOldAttribute()]]. +[[yii\db\ActiveRecord::getOldAttributes()|getOldAttributes()]] 或者 [[yii\db\ActiveRecord::getOldAttribute()|getOldAttribute()]]。 > 注:属性新旧值的比较是用 `===` 操作符,所以一样的值但类型不同, > 依然被认为是脏的。当模型从 HTML 表单接收用户输入时,通常会出现这种情况, > 其中每个值都表示为一个字符串类型。 -> 为了确保正确的类型,比如,整型需要用 [过滤验证器](input-validation.md#data-filtering): +> 为了确保正确的类型,比如,整型需要用[过滤验证器](input-validation.md#data-filtering): > `['attributeName', 'filter', 'filter' => 'intval']`。其他 PHP 类型转换函数一样适用,像 > [intval()](http://php.net/manual/en/function.intval.php), [floatval()](http://php.net/manual/en/function.floatval.php), -> [boolval](http://php.net/manual/en/function.boolval.php), 等等 +> [boolval](http://php.net/manual/en/function.boolval.php),等等 -### 默认属性值 +### 默认属性值(Default Attribute Values) 某些表列可能在数据库中定义了默认值。有时,你可能想预先填充 -具有这些默认值的 AR 实例的 Web 表单。 为了避免再次写入相同的默认值, +具有这些默认值的 Active Record 实例的 Web 表单。 为了避免再次写入相同的默认值, 您可以调用 [[yii\db\ActiveRecord::loadDefaultValues()|loadDefaultValues()]] 来填充 DB 定义的默认值 -进入相应的 AR 属性: +进入相应的 Active Record 属性: ```php $customer = new Customer(); @@ -470,11 +489,11 @@ $customer->loadDefaultValues(); ``` -### 属性类型转换 +### 属性类型转换(Attributes Typecasting) -在查询结果填充 [[yii\db\ActiveRecord]] 活动记录时,将自动对其属性值执行类型转换,基于 +在查询结果填充 [[yii\db\ActiveRecord]] 时,将自动对其属性值执行类型转换,基于 [数据库表模式](db-dao.md#database-schema) 中的信息。 这允许从数据表中获取数据, -声明为整型的,使用 PHP 整型填充 AR 实例,布尔值(boolean)的也用布尔值填充 AR 实例,等等。 +声明为整型的,使用 PHP 整型填充 ActiveRecord 实例,布尔值(boolean)的也用布尔值填充,等等。 但是,类型转换机制有几个限制: * 浮点值不被转换,并且将被表示为字符串,否则它们可能会使精度降低。 @@ -490,11 +509,37 @@ $customer->loadDefaultValues(); > Tip: 你可以使用 [[yii\behaviors\AttributeTypecastBehavior]] 来简化属性的类型转换 在 ActiveRecord 验证或者保存过程中。 + +从2.0.14开始,Yii ActiveRecord 支持了更多的复杂数据类型,例如 JSON 或多维数组。 + +#### MySQL 和 PostgreSQL 中的 JSON(JSON in MySQL and PostgreSQL) +数据填充后,基于 JSON 标准解码规则, +来自 JSON 列的值将自动解码。 -### Updating Multiple Rows +另一方面,为了将属性值保存到 JSON 列中,ActiveRecord 会自动创建一个 [[yii\db\JsonExpression|JsonExpression]] 对象, +这对象将在 [QueryBuilder](db-query-builder.md) 层被编码成 JSON 字符串。 -上述方法都可以用于单个 AR 实例,以插入或更新单条 +#### PostgreSQL 中的数组(Arrays in PostgreSQL) + +数据填充后,来自 Array 列的值将自动从 PgSQL 的编码值解码为 一个 [[yii\db\ArrayExpression|ArrayExpression]] +对象。它继承于 PHP 的 `ArrayAccess` 接口,所以你可以把它当作一个数组用,或者调用 `->getValue()` 来获取数组本身。 + +另一方面,为了将属性值保存到数组列,ActiveRecord 会自动创建一个 [[yii\db\ArrayExpression|ArrayExpression]] 对象, +这对象将在 [QueryBuilder](db-query-builder.md) 中被编码成数组的 PgSQL 字符串表达式。 + +你还可以这样使用 JSON 列的条件: + +```php +$query->andWhere(['=', 'json', new ArrayExpression(['foo' => 'bar']) +``` + +要详细了解表达式构建系统,可以访问 [Query Builder – 增加自定义条件和语句](db-query-builder.md#adding-custom-conditions-and-expressions) +文章。 + +### 更新多个数据行(Updating Multiple Rows) + +上述方法都可以用于单个 Active Record 实例,以插入或更新单条 表数据行。 要同时更新多个数据行,你应该调用 [[yii\db\ActiveRecord::updateAll()|updateAll()]] 这是一个静态方法。 @@ -512,9 +557,9 @@ Customer::updateAllCounters(['age' => 1]); ``` -## 删除数据 +## 删除数据(Deleting Data) -要删除单行数据,首先获取与该行对应的 AR 实例,然后调用 +要删除单行数据,首先获取与该行对应的 Active Record 实例,然后调用 [[yii\db\ActiveRecord::delete()]] 方法。 ```php @@ -528,43 +573,43 @@ $customer->delete(); Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]); ``` -> Tip: 不要随意使用 [[yii\db\ActiveRecord::deleteAll()|deleteAll()]] 它真的会 - 清空你表里的数据,因为你指不定啥时候犯二。 +> Tip: 调用 [[yii\db\ActiveRecord::deleteAll()|deleteAll()]] 时要非常小心,因为如果在指定条件时出错, + 它可能会完全擦除表中的所有数据。 -## AR 的生命周期 +## Active Record 的生命周期(Active Record Life Cycles) -当你实现各种功能的时候,会发现了解 AR 的生命周期很重要。 +当你实现各种功能的时候,会发现了解 Active Record 的生命周期很重要。 在每个生命周期中,一系列的方法将被调用执行,您可以重写这些方法 -以定制你要的生命周期。您还可以响应触发某些 AR 事件 -以便在生命周期中注入您的自定义代码。这些事件在开发 AR 的 [行为](concept-behaviors.md)时特别有用, -通过行为可以定制 AR 生命周期的 。 +以定制你要的生命周期。您还可以响应触发某些 Active Record 事件 +以便在生命周期中注入您的自定义代码。这些事件在开发 Active Record 的 [行为](concept-behaviors.md)时特别有用, +通过行为可以定制 Active Record 生命周期的 。 -下面,我们将总结各种 AR 的生命周期,以及生命周期中 +下面,我们将总结各种 Active Record 的生命周期,以及生命周期中 所涉及的各种方法、事件。 -### 实例化生命周期 +### 实例化生命周期(New Instance Life Cycle) -当通过 `new` 操作符新建一个 AR 实例时,会发生以下生命周期: +当通过 `new` 操作符新建一个 Active Record 实例时,会发生以下生命周期: 1. 类的构造函数调用. 2. [[yii\db\ActiveRecord::init()|init()]]:触发 [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] 事件。 -### 查询数据生命周期 +### 查询数据生命周期(Querying Data Life Cycle) -当通过 [查询方法](#querying-data) 查询数据时,每个新填充出来的 AR 实例 +当通过 [查询方法](#querying-data) 查询数据时,每个新填充出来的 Active Record 实例 将发生下面的生命周期: -1. 类的构造函数调用. +1. 类的构造函数调用。 2. [[yii\db\ActiveRecord::init()|init()]]:触发 [[yii\db\ActiveRecord::EVENT_INIT|EVENT_INIT]] 事件。 3. [[yii\db\ActiveRecord::afterFind()|afterFind()]]:触发 [[yii\db\ActiveRecord::EVENT_AFTER_FIND|EVENT_AFTER_FIND]] 事件。 -### 保存数据生命周期 +### 保存数据生命周期(Saving Data Life Cycle) -当通过 [[yii\db\ActiveRecord::save()|save()]] 插入或更新 AR 实例时 +当通过 [[yii\db\ActiveRecord::save()|save()]] 插入或更新 Active Record 实例时 会发生以下生命周期: 1. [[yii\db\ActiveRecord::beforeValidate()|beforeValidate()]]:触发 @@ -583,9 +628,9 @@ Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]); 或者 [[yii\db\ActiveRecord::EVENT_AFTER_UPDATE|EVENT_AFTER_UPDATE]] 事件。 -### 删除数据生命周期 +### 删除数据生命周期(Deleting Data Life Cycle) -当通过 [[yii\db\ActiveRecord::delete()|delete()]] 删除 AR 实例时, +当通过 [[yii\db\ActiveRecord::delete()|delete()]] 删除 Active Record 实例时, 会发生以下生命周期: 1. [[yii\db\ActiveRecord::beforeDelete()|beforeDelete()]]:触发 @@ -597,24 +642,24 @@ Customer::deleteAll(['status' => Customer::STATUS_INACTIVE]); > Tip: 调用以下方法则不会启动上述的任何生命周期, -> 因为这些方法直接操作数据库,而不是基于 AR 模型: +> 因为这些方法直接操作数据库,而不是基于 Active Record 模型: > > - [[yii\db\ActiveRecord::updateAll()]] > - [[yii\db\ActiveRecord::deleteAll()]] > - [[yii\db\ActiveRecord::updateCounters()]] > - [[yii\db\ActiveRecord::updateAllCounters()]] -### 刷新数据生命周期 +### 刷新数据生命周期(Refreshing Data Life Cycle) -当通过 [[yii\db\ActiveRecord::refresh()|refresh()]] 刷新 AR 实例时, +当通过 [[yii\db\ActiveRecord::refresh()|refresh()]] 刷新 Active Record 实例时, 如刷新成功方法返回 `true`,那么 [[yii\db\ActiveRecord::EVENT_AFTER_REFRESH|EVENT_AFTER_REFRESH]] 事件将被触发。 -## 事务操作 +## 事务操作(Working with Transactions) -AR 活动记录有两种方式来使用 [事务](db-dao.md#performing-transactions)。 +Active Record 有两种方式来使用[事务](db-dao.md#performing-transactions)。 -第一种方法是在事务块中显式地包含 AR 的各个方法调用,如下所示, +第一种方法是在事务块中显式地包含 Active Record 的各个方法调用,如下所示, ```php $customer = Customer::findOne(123); @@ -678,7 +723,7 @@ class Customer extends ActiveRecord 这个事务方法的原理是:相应的事务在调用 [[yii\db\ActiveRecord::beforeSave()|beforeSave()]] 方法时开启, 在调用 [[yii\db\ActiveRecord::afterSave()|afterSave()]] 方法时被提交。 -## 乐观锁 +## 乐观锁(Optimistic Locks) 乐观锁是一种防止此冲突的方法:一行数据 同时被多个用户更新。例如,同一时间内,用户 A 和用户 B 都在编辑 @@ -694,15 +739,15 @@ class Customer extends ActiveRecord 使用乐观锁的步骤, -1. 在与 AR 类相关联的 DB 表中创建一个列,以存储每行的版本号。 +1. 在与 Active Record 类相关联的 DB 表中创建一个列,以存储每行的版本号。 这个列应当是长整型(在 MySQL 中是 `BIGINT DEFAULT 0`)。 2. 重写 [[yii\db\ActiveRecord::optimisticLock()]] 方法返回这个列的命名。 3. 在用于用户填写的 Web 表单中,添加一个隐藏字段(hidden field)来存储正在更新的行的当前版本号。 - (AR 类中)版本号这个属性你要自行写进 rules() 方法并自己验证一下。 -4. 在使用 AR 更新数据的控制器动作中,要捕获(try/catch) [[yii\db\StaleObjectException]] 异常。 + (Active Record 类中)版本号这个属性你要自行写进 rules() 方法并自己验证一下。 +4. 在使用 Active Record 更新数据的控制器动作中,要捕获(try/catch) [[yii\db\StaleObjectException]] 异常。 实现一些业务逻辑来解决冲突(例如合并更改,提示陈旧的数据等等)。 - -举个栗子,假定版本列被命名为 `version`。您可以使用下面的代码来实现乐观锁。 + +例如,假定版本列被命名为 `version`。您可以使用下面的代码来实现乐观锁。 ```php @@ -737,18 +782,18 @@ public function actionUpdate($id) ``` -## 使用关联数据 +## 使用关联数据(Working with Relational Data) -除了处理单个数据库表之外,AR 还可以将相关数据集中进来, +除了处理单个数据库表之外,Active Record 还可以将相关数据集中进来, 使其可以通过原始数据轻松访问。 例如,客户数据与订单数据相关 因为一个客户可能已经存放了一个或多个订单。这种关系通过适当的声明, 你可以使用 `$customer->orders` 表达式访问客户的订单信息 -这表达式将返回包含 `Order` AR 实例的客户订单信息的数组。 +这表达式将返回包含 `Order` Active Record 实例的客户订单信息的数组。 -### 声明关联关系 +### 声明关联关系(Declaring Relations) -你必须先在 AR 类中定义关联关系,才能使用 AR 的关联数据。 +你必须先在 Active Record 类中定义关联关系,才能使用 Active Record 的关联数据。 简单地为每个需要定义关联关系声明一个 *关联方法* 即可,如下所示, ```php @@ -784,12 +829,12 @@ class Order extends ActiveRecord - 关联的对应关系:通过调用 [[yii\db\ActiveRecord::hasMany()|hasMany()]] 或者 [[yii\db\ActiveRecord::hasOne()|hasOne()]] 指定。在上面的例子中,您可以很容易看出这样的关联声明: 一个客户可以有很多订单,而每个订单只有一个客户。 -- 相关联 AR 类名:用来指定为 [[yii\db\ActiveRecord::hasMany()|hasMany()]] 或者 +- 相关联 Active Record 类名:用来指定为 [[yii\db\ActiveRecord::hasMany()|hasMany()]] 或者 [[yii\db\ActiveRecord::hasOne()|hasOne()]] 方法的第一个参数。 推荐的做法是调用 `Xyz::className()` 来获取类名称的字符串,以便您 可以使用 IDE 的自动补全,以及让编译阶段的错误检测生效。 - 两组数据的关联列:用以指定两组数据相关的列(hasOne()/hasMany() 的第二个参数)。 - 数组的值填的是主数据的列(当前要声明关联的 AR 类为主数据), + 数组的值填的是主数据的列(当前要声明关联的 Active Record 类为主数据), 而数组的键要填的是相关数据的列。 一个简单的口诀,先附表的主键,后主表的主键。 @@ -797,7 +842,7 @@ class Order extends ActiveRecord (译者注:hasMany() 的第二个参数,这个数组键值顺序不要弄反了) -### 访问关联数据 +### 访问关联数据(Accessing Relational Data) 定义了关联关系后,你就可以通过关联名访问相应的关联数据了。就像 访问一个由关联方法定义的对象一样,具体概念请查看 [属性](concept-properties.md)。 @@ -816,9 +861,9 @@ $orders = $customer->orders; [属性](concept-properties.md) 那样访问 `xyz`。注意这个命名是区分大小写的。 如果使用 [[yii\db\ActiveRecord::hasMany()|hasMany()]] 声明关联关系,则访问此关联属性 -将返回相关的 AR 实例的数组; +将返回相关的 Active Record 实例的数组; 如果使用 [[yii\db\ActiveRecord::hasOne()|hasOne()]] 声明关联关系,访问此关联属性 -将返回相关的 AR 实例,如果没有找到相关数据的话,则返回 `null`。 +将返回相关的 Active Record 实例,如果没有找到相关数据的话,则返回 `null`。 当你第一次访问关联属性时,将执行 SQL 语句获取数据,如 上面的例子所示。如果再次访问相同的属性,将返回先前的结果,而不会重新执行 @@ -828,7 +873,7 @@ SQL 语句。要强制重新执行 SQL 语句,你应该先 unset 这个关联 > Tip: 虽然这个概念跟 这个 [属性](concept-properties.md) 特性很像, > 但是还是有一个很重要的区别。普通对象属性的属性值与其定义的 getter 方法的类型是相同的。 > 而关联方法返回的是一个 [[yii\db\ActiveQuery]] 活动查询生成器的实例。只有当访问关联属性的的时候, -> 才会返回 [[yii\db\ActiveRecord]] AR 实例,或者 AR 实例组成的数组。 +> 才会返回 [[yii\db\ActiveRecord]] Active Record 实例,或者 Active Record 实例组成的数组。 > > ```php > $customer->orders; // 获得 `Order` 对象的数组 @@ -838,7 +883,7 @@ SQL 语句。要强制重新执行 SQL 语句,你应该先 unset 这个关联 > 这对于创建自定义查询很有用,下一节将对此进行描述。 -### 动态关联查询 +### 动态关联查询(Dynamic Relational Query) 由于关联方法返回 [[yii\db\ActiveQuery]] 的实例,因此你可以在执行 DB 查询之前, 使用查询构建方法进一步构建此查询。例如, @@ -882,10 +927,10 @@ $orders = $customer->bigOrders; ``` -### 中间关联表 +### 中间关联表(Relations via a Junction Table) 在数据库建模中,当两个关联表之间的对应关系是多对多时, -通常会引入一个 [连接表](https://en.wikipedia.org/wiki/Junction_table)。例如,`order` 表 +通常会引入一个[连接表](https://en.wikipedia.org/wiki/Junction_table)。例如,`order` 表 和 `item` 表可以通过名为 `order_item` 的连接表相关联。一个 order 将关联多个 order items, 而一个 order item 也会关联到多个 orders。 @@ -936,10 +981,45 @@ $items = $order->items; ``` -### 延迟加载和即时加载(又称惰性加载与贪婪加载) +### 通过多个表来连接关联声明(Chaining relation definitions via multiple tables) + +通过使用 [[yii\db\ActiveQuery::via()|via()]] 方法,它还可以通过多个表来定义关联声明。 +再考虑考虑上面的例子,我们有 `Customer`, `Order`, 和 `Item` 类。 +我们可以添加一个关联关系到 `Customer` 类,这个关联可以列出了 `Customer`(客户) 的订单下放置的所有 `Item`(商品), +这个关联命名为 `getPurchasedItems()`,关联声明如下代码示例所示: + +```php +class Customer extends ActiveRecord +{ + // ... + + public function getPurchasedItems() + { + // 客户的商品,将 Item 中的 'id' 列与 OrderItem 中的 'item_id' 相匹配 + return $this->hasMany(Item::className(), ['id' => 'item_id']) + ->via('orderItems'); + } + + public function getOrderItems() + { + // 客户订单中的商品,将 `Order` 的 'id' 列和 OrderItem 的 'order_id' 列相匹配 + return $this->hasMany(OrderItem::className(), ['order_id' => 'id']) + ->via('orders'); + } + + public function getOrders() + { + // 见上述列子 + return $this->hasMany(Order::className(), ['customer_id' => 'id']); + } +} +``` + + +### 延迟加载和即时加载(Lazy Loading and Eager Loading) 在 [访问关联数据](#accessing-relational-data) 中,我们解释说可以像问正常的对象属性那样 -访问 AR 实例的关联属性。SQL 语句仅在 +访问 Active Record 实例的关联属性。SQL 语句仅在 你第一次访问关联属性时执行。我们称这种关联数据访问方法为 *延迟加载*。 例如, @@ -954,7 +1034,7 @@ $orders = $customer->orders; $orders2 = $customer->orders; ``` -延迟加载使用非常方便。但是,当你需要访问相同的具有多个 AR 实例的关联属性时, +延迟加载使用非常方便。但是,当你需要访问相同的具有多个 Active Record 实例的关联属性时, 可能会遇到性能问题。请思考一下以下代码示例。 有多少 SQL 语句会被执行? @@ -988,11 +1068,11 @@ foreach ($customers as $customer) { } ``` -通过调用 [[yii\db\ActiveQuery::with()]] 方法,你使 AR 在一条 SQL 语句里就返回了这 100 位客户的订单。 +通过调用 [[yii\db\ActiveQuery::with()]] 方法,你使 Active Record 在一条 SQL 语句里就返回了这 100 位客户的订单。 结果就是,你把要执行的 SQL 语句从 101 减少到 2 条! 你可以即时加载一个或多个关联。 你甚至可以即时加载 *嵌套关联* 。嵌套关联是一种 -在相关的 AR 类中声明的关联。例如,`Customer` 通过 `orders` 关联属性 与 `Order` 相关联, +在相关的 Active Record 类中声明的关联。例如,`Customer` 通过 `orders` 关联属性 与 `Order` 相关联, `Order` 与 `Item` 通过 `items` 关联属性相关联。 当查询 `Customer` 时,您可以即时加载 通过嵌套关联符 `orders.items` 关联的 `items`。 @@ -1009,10 +1089,10 @@ $orders= $customers[0]->orders; // 没有任何的 SQL 执行 $country = $customers[0]->country; -// eager loading "orders" and the nested relation "orders.items" +// 即时加载“订单”和嵌套关系“orders.items” $customers = Customer::find()->with('orders.items')->all(); -// access the items of the first order of the first customer -// no SQL executed +// 访问第一个客户的第一个订单中的商品 +// 没有 SQL 查询执行 $items = $customers[0]->orders[0]->items; ``` @@ -1056,7 +1136,7 @@ $customers = Customer::find()->with([ > ``` -### 关联关系的 JOIN 查询 +### 关联关系的 JOIN 查询(Joining with Relations) > Tip: 这小节的内容仅仅适用于关系数据库, 比如 MySQL,PostgreSQL 等等。 @@ -1099,7 +1179,9 @@ $customers = Customer::find() 如果你想要的连接类型是 `INNER JOIN`,你可以直接用 [[yii\db\ActiveQuery::innerJoinWith()|innerJoinWith()]] 方法代替。 调用 [[yii\db\ActiveQuery::joinWith()|joinWith()]] 方法会默认 [即时加载](#lazy-eager-loading) 相应的关联数据。 -如果你不需要那些关联数据,你可以指定它的第二个参数 $eagerLoading` 为 `false`。 +如果你不需要那些关联数据,你可以指定它的第二个参数 `$eagerLoading` 为 `false`。 + +> Note: 即使在启用即时加载的情况下使用 [[yii\db\ActiveQuery::joinWith()|joinWith()]] 或 [[yii\db\ActiveQuery::innerJoinWith()|innerJoinWith()]],相应的关联数据也**不会**从这个 `JOIN` 查询的结果中填充。 因此,每个连接关系还有一个额外的查询,正如[即时加载](#lazy-eager-loading)部分所述。 和 [[yii\db\ActiveQuery::with()|with()]] 一样,你可以 join 多个关联表;你可以动态的自定义 你的关联查询;你可以使用嵌套关联进行 join。你也可以将 [[yii\db\ActiveQuery::with()|with()]] @@ -1130,14 +1212,14 @@ $customers = Customer::find()->joinWith([ ``` 以上查询取出 *所有* 客户,并为每个客户取回所有活跃订单。 -港真,这与我们之前的例子不同,后者仅取出至少有一个活跃订单的客户。 +请注意,这与我们之前的例子不同,后者仅取出至少有一个活跃订单的客户。 > Tip: 当通过 [[yii\db\ActiveQuery::onCondition()|onCondition()]] 修改 [[yii\db\ActiveQuery]] 时, 如果查询涉及到 JOIN 查询,那么条件将被放在 `ON` 部分。如果查询不涉及 JOIN ,条件将自动附加到查询的 `WHERE` 部分。 因此,它可以只包含 包含了关联表的列 的条件。(译者注:意思是 onCondition() 中可以只写关联表的列,主表的列写不写都行) -#### Relation table aliases +#### 关联表别名(Relation table aliases) 如前所述,当在查询中使用 JOIN 时,我们需要消除列名的歧义。因此通常为一张表定义 一个别名。可以通过以下列方式自定义关联查询来设置关联查询的别名: @@ -1169,9 +1251,9 @@ $query->joinWith(['orders o' => function($q) { ->where('o.amount > 100'); ``` -### 反向关联 +### 反向关联(Inverse Relations) -两个 AR 类之间的关联声明往往像 *倒数* 那样。例如,`Customer` 是 +两个 Active Record 类之间的关联声明往往是相互关联的。例如,`Customer` 是 通过 `orders` 关联到 `Order` ,而`Order` 通过 `customer` 又关联回到了 `Customer`。 ```php @@ -1192,7 +1274,7 @@ class Order extends ActiveRecord } ``` -现在开一下脑洞: +现在考虑下面的一段代码: ```php // SELECT * FROM `customer` WHERE `id` = 123 @@ -1242,15 +1324,15 @@ $customer2 = $order->customer; echo $customer2 === $customer ? 'same' : 'not the same'; ``` -> Tip: 反向关联不能用在有 [连接表](#junction-table) 关联声明中。 +> Note: 反向关联不能用在有 [连接表](#junction-table) 关联声明中。 也就是说,如果一个关联关系通过 [[yii\db\ActiveQuery::via()|via()]] 或 [[yii\db\ActiveQuery::viaTable()|viaTable()]] 声明, 你就不能再调用 [[yii\db\ActiveQuery::inverseOf()|inverseOf()]] 了。 -## 保存关联数据 +## 保存关联数据(Saving Relations) 在使用关联数据时,您经常需要建立不同数据之间的关联或销毁 -现有关联。这需要为定义的关联的列设置正确的值。通过使用 AR 活动记录, +现有关联。这需要为定义的关联的列设置正确的值。通过使用 Active Record, 你就可以编写如下代码: ```php @@ -1264,7 +1346,7 @@ $order->customer_id = $customer->id; $order->save(); ``` -AR 提供了 [[yii\db\ActiveRecord::link()|link()]] 方法,可以更好地完成此任务: +Active Record 提供了 [[yii\db\ActiveRecord::link()|link()]] 方法,可以更好地完成此任务: ```php $customer = Customer::findOne(123); @@ -1276,15 +1358,15 @@ $order->link('customer', $customer); ``` [[yii\db\ActiveRecord::link()|link()]] 方法需要指定关联名 -和要建立关联的目标 AR 实例。该方法将修改属性的值 -以连接两个 AR 实例,并将其保存到数据库。在上面的例子中,它将设置 `Order` 实例的 `customer_id` 属性 +和要建立关联的目标 Active Record 实例。该方法将修改属性的值 +以连接两个 Active Record 实例,并将其保存到数据库。在上面的例子中,它将设置 `Order` 实例的 `customer_id` 属性 为 `Customer` 实例的 `id` 属性的值,然后保存 到数据库。 -> Tip: 你不能 link() 两个新的 AR 实例。(译者注:其中的一个必须是数据库中查询出来的) +> Note: 你不能关联两个新的 Active Record 实例。 -当一个关联关系通过 [连接表](#junction-table) 定义时,此 [[yii\db\ActiveRecord::link()|link()]] 方法更能体现在党的领导下的中国特色社会主义的优越性。 -例如,你可以使用以下代码 link() `Order` 实例 +使用 [[yii\db\ActiveRecord::link()|link()]] 的好处在通过 [junction table](#junction-table) 定义关系时更加明显。 +例如,你可以使用以下代码关联 `Order` 实例 和 `Item` 实例: ```php @@ -1293,12 +1375,12 @@ $order->link('items', $item); 上述代码会自动在 `order_item` 关联表中插入一行,以关联 order 和 item 这两个数据记录。 -> Tip: [[yii\db\ActiveRecord::link()|link()]] 方法在保存相应的 AR 实例时, +> Info: [[yii\db\ActiveRecord::link()|link()]] 方法在保存相应的 Active Record 实例时, 将不会执行任何数据验证。在调用此方法之前, 您应当验证所有的输入数据。 [[yii\db\ActiveRecord::link()|link()]] 方法的反向操作是 [[yii\db\ActiveRecord::unlink()|unlink()]] 方法, -这将可以断掉两个 AR 实例间的已经存在了的关联关系。例如, +这将可以断掉两个 Active Record 实例间的已经存在了的关联关系。例如, ```php $customer = Customer::find()->with('orders')->where(['id' => 123])->one(); @@ -1314,9 +1396,9 @@ $customer->unlink('orders', $customer->orders[0]); 连接表中的外键或相应的行被删除。 -## 跨数据库关联 +## 跨数据库关联(Cross-Database Relations) -AR 活动记录允许您在不同数据库驱动的 AR 类之间声明关联关系。 +Active Record 允许您在不同数据库驱动的 Active Record 类之间声明关联关系。 这些数据库可以是不同的类型(例如 MySQL 和 PostgreSQL ,或是 MS SQL 和 MongoDB),它们也可以运行在 不同的服务器上。你可以使用相同的语法来执行关联查询。例如, @@ -1356,13 +1438,13 @@ $customers = Customer::find()->with('comments')->all(); 本节中描述的大多数关联查询功能,你都可以抄一抄。 -> Tip: [[yii\db\ActiveQuery::joinWith()|joinWith()]] 这个功能限制于某些数据库是否支持跨数据库 JOIN 查询。 - 因此,你再上述的代码里就不能用此方法了,因为 MongoDB 不知道 JOIN 查询。 +> Note: [[yii\db\ActiveQuery::joinWith()|joinWith()]] 这个功能限制于某些数据库是否支持跨数据库 JOIN 查询。 + 因此,你再上述的代码里就不能用此方法了,因为 MongoDB 不支持 JOIN 查询。 -## 自定义查询类 +## 自定义查询类(Customizing Query Classes) -默认情况下,[[yii\db\ActiveQuery]] 支持所有 AR 查询。要在 AR 类中使用自定义的查询类, +默认情况下,[[yii\db\ActiveQuery]] 支持所有 Active Record 查询。要在 Active Record 类中使用自定义的查询类, 您应该重写 [[yii\db\ActiveRecord::find()]] 方法并返回一个你自定义查询类的实例。 例如, @@ -1410,7 +1492,7 @@ class CommentQuery extends ActiveQuery } ``` -> Tip: 作为 [[yii\db\ActiveQuery::onCondition()|onCondition()]] 方法的替代方案,你应当 +> Note: 作为 [[yii\db\ActiveQuery::onCondition()|onCondition()]] 方法的替代方案,你应当 调用 [[yii\db\ActiveQuery::andOnCondition()|andOnCondition()]] 或 [[yii\db\ActiveQuery::orOnCondition()|orOnCondition()]] 方法来附加新增的条件,不然在一个新定义的查询方法,已存在的条件可能会被覆盖。 然后你就可以先下面这样构建你的查询了: @@ -1421,7 +1503,7 @@ $inactiveComments = Comment::find()->active(false)->all(); ``` > Tip: 在大型项目中,建议您使用自定义查询类来容纳大多数与查询相关的代码, - 以使 AR 类保持简洁。 + 以使 Active Record 类保持简洁。 您还可以在 `Comment` 关联关系的定义中或在执行关联查询时,使用刚刚新建查询构建方法: @@ -1456,12 +1538,12 @@ $customers = Customer::find()->joinWith([ 你依然可以使用自定义查询类、查询方法来达到一样的效果。 -## 选择额外的字段 +## 选择额外的字段(Selecting extra fields) -当 AR 实例从查询结果中填充时,从数据结果集中, +当 Active Record 实例从查询结果中填充时,从数据结果集中, 其属性的值将被相应的列填充。 -你可以从查询中获取其他列或值,并将其存储在 AR 活动记录中。 +你可以从查询中获取其他列或值,并将其存储在 Active Record 活动记录中。 例如,假设我们有一个名为 `room` 的表,其中包含有关酒店可用房间的信息。 每个房间使用字段 `length`,`width`,`height` 存储有关其空间大小的信息。 想象一下,我们需要检索出所有可用房间的列表,并按照体积大小倒序排列。 @@ -1570,8 +1652,8 @@ class Room extends \yii\db\ActiveRecord } ``` -当 select 查询不提供 volume 体积时,这模型将能够自动计算体积的值出来 -,当访问模型的属性的时候。 +当 select 查询不提供 volume 体积时,这模型将能够自动计算体积的值出来, +当访问模型的属性的时候。 当定义关联关系的时候,你也可以计算聚合字段: @@ -1607,8 +1689,8 @@ class Customer extends \yii\db\ActiveRecord } ``` -使用此代码,如果 'select' 语句中存在 'ordersCount' - 它会从查询结果集获取数据填充 `Customer::ordersCount` 属性, -,否则它会在被访问的时候,使用 `Customer::orders` 关联按需计算。 +使用此代码,如果 'select' 语句中存在 'ordersCount' - 它会从查询结果集获取数据填充 `Customer::ordersCount` 属性, +否则它会在被访问的时候,使用 `Customer::orders` 关联按需计算。 这种方法也适用于创建一些关联数据的快捷访问方式,特别是对于聚合。 例如: diff --git a/docs/guide-zh-CN/db-dao.md b/docs/guide-zh-CN/db-dao.md index 25d0747..e570a60 100644 --- a/docs/guide-zh-CN/db-dao.md +++ b/docs/guide-zh-CN/db-dao.md @@ -1,5 +1,5 @@ -数据库访问 (DAO) -======== +数据库访问对象(Database Access Objects) +===================================== Yii 包含了一个建立在 PHP PDO 之上的数据访问层 (DAO)。DAO为不同的数据库提供了一套统一的API。 其中 `ActiveRecord` 提供了数据库与模型(MVC 中的 M,Model) 的交互,`QueryBuilder` 用于创建动态的查询语句。 @@ -23,10 +23,9 @@ Yii DAO 支持下列现成的数据库: [instruction provided by community](https://github.com/yiisoft/yii2/issues/10975#issuecomment-248479268) to compile it or use [PDO emulation layer](https://github.com/taq/pdooci). -## 创建数据库连接 +## 创建数据库连接(Creating DB Connections) -想要访问数据库, -你首先需要通过创建一个 [[yii\db\Connection]] 实例来与之建立连接。 +想要访问数据库,你首先需要通过创建一个 [[yii\db\Connection]] 实例来与之建立连接。 ```php $db = new yii\db\Connection([ @@ -38,8 +37,7 @@ $db = new yii\db\Connection([ ``` 因为数据库连接经常需要在多个地方使用到, -一个常见的做法是以[应用组件](structure-application-components.md)的方式来配置它, -如下: +一个常见的做法是以[应用组件](structure-application-components.md)的方式来配置它,如下: ```php return [ @@ -64,8 +62,7 @@ return [ 配置数据库连接时, 你应该总是通过 [[yii\db\Connection::dsn|dsn]] 属性来指明它的数据源名称 (DSN) 。 不同的数据库有着不同的 DSN 格式。 -请参考 [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) 来获得更多细节。 -下面是一些例子: +请参考 [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) 来获得更多细节。下面是一些例子: * MySQL, MariaDB: `mysql:host=localhost;dbname=mydatabase` * SQLite: `sqlite:/path/to/database/file` @@ -76,10 +73,8 @@ return [ * MS SQL Server (via mssql driver): `mssql:host=localhost;dbname=mydatabase` * Oracle: `oci:dbname=//localhost:1521/mydatabase` -请注意,如果你是通过 ODBC 来连接数据库, -你应该配置 [[yii\db\Connection::driverName]] 属性, -以便 Yii 能够知道实际的数据库种类。 -例如: +请注意,如果你是通过 ODBC 来连接数据库,你应该配置 [[yii\db\Connection::driverName]] 属性, +以便 Yii 能够知道实际的数据库种类。例如: ```php 'db' => [ @@ -92,15 +87,14 @@ return [ ``` 除了 [[yii\db\Connection::dsn|dsn]] 属性, -你常常需要配置 [[yii\db\Connection::username|username]] 和 [[yii\db\Connection::password|password]]。 -请参考 [[yii\db\Connection]] 来获取完整的可配置属性列表。 +你常常需要配置 [[yii\db\Connection::username|username]] 和 [[yii\db\Connection::password|password]]。请参考 [[yii\db\Connection]] 来获取完整的可配置属性列表。 -> Info: 当你实例化一个 DB Connection 时, -直到你第一次执行 SQL 或者你明确地调用 [[yii\db\Connection::open()|open()]] 方法时, -才建立起实际的数据库连接。 +> Info: 当你实例化一个 DB Connection 时,直到你第一次执行 SQL 或者你明确地调用 [[yii\db\Connection::open()|open()]] 方法时, + 才建立起实际的数据库连接。 > Tip: 有时你可能想要在建立起数据库连接时立即执行一些语句来初始化一些环境变量 (比如设置时区或者字符集), -> 你可以通过为数据库连接的 [[yii\db\Connection::EVENT_AFTER_OPEN|afterOpen]] 事件注册一个事件处理器来达到目的。 +> 你可以通过为数据库连接的 [[yii\db\Connection::EVENT_AFTER_OPEN|afterOpen]] +> 事件注册一个事件处理器来达到目的。 > 你可以像这样直接在应用配置中注册处理器: > > ```php @@ -114,13 +108,12 @@ return [ > ``` -## 执行 SQL 查询 +## 执行 SQL 查询(Executing SQL Queries) -一旦你拥有了 DB Connection 实例, -你可以按照下列步骤来执行 SQL 查询: +一旦你拥有了 DB Connection 实例,你可以按照下列步骤来执行 SQL 查询: -1. 使用纯SQL查询来创建出 [[yii\db\Command]]; -2. 绑定参数 (可选的); +1. 使用纯SQL查询来创建出 [[yii\db\Command]]; +2. 绑定参数 (可选的); 3. 调用 [[yii\db\Command]] 里 SQL 执行方法中的一个。 下列例子展示了几种不同的从数据库取得数据的方法: @@ -147,12 +140,11 @@ $count = Yii::$app->db->createCommand('SELECT COUNT(*) FROM post') ->queryScalar(); ``` -> Note: 为了保持精度, -> 即使对应的数据库列类型为数值型, +> Note: 为了保持精度,即使对应的数据库列类型为数值型, > 所有从数据库取得的数据都被表现为字符串。 -### 绑定参数 +### 绑定参数(Binding Parameters) 当使用带参数的 SQL 来创建数据库命令时, 你几乎总是应该使用绑定参数的方法来防止 SQL 注入攻击,例如: @@ -164,14 +156,14 @@ $post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status ->queryOne(); ``` -在 SQL 语句中, -你可以嵌入一个或多个参数占位符(例如,上述例子中的 `:id` )。 +在 SQL 语句中,你可以嵌入一个或多个参数占位符(例如,上述例子中的 `:id` )。 一个参数占位符应该是以冒号开头的字符串。 之后你可以调用下面绑定参数的方法来绑定参数值: * [[yii\db\Command::bindValue()|bindValue()]]:绑定一个参数值 * [[yii\db\Command::bindValues()|bindValues()]]:在一次调用中绑定多个参数值 -* [[yii\db\Command::bindParam()|bindParam()]]:与 [[yii\db\Command::bindValue()|bindValue()]] 相似,但是也支持绑定参数引用。 +* [[yii\db\Command::bindParam()|bindParam()]]:与 [[yii\db\Command::bindValue()|bindValue()]] + 相似,但是也支持绑定参数引用。 下面的例子展示了几个可供选择的绑定参数的方法: @@ -187,11 +179,8 @@ $post = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id AND status ``` 绑定参数是通过 [预处理语句](http://php.net/manual/en/mysqli.quickstart.prepared-statements.php) 实现的。 -除了防止 SQL 注入攻击, -它也可以通过一次预处理 SQL 语句, -使用不同参数多次执行, -来提升性能。 -例如: +除了防止 SQL 注入攻击,它也可以通过一次预处理 SQL 语句, +使用不同参数多次执行,来提升性能。例如: ```php $command = Yii::$app->db->createCommand('SELECT * FROM post WHERE id=:id'); @@ -220,13 +209,16 @@ $post2 = $command->queryOne(); 然后在之后的每次执行前改变变量的值(这通常是用循环来完成的)。 以这种方式执行查询比为每个不同的参数值执行一次新的查询要高效得多得多。 +> Info: 参数绑定仅用于需要将值插入到包含普通SQL的字符串中的地方。 +> 在 [Query Builder](db-query-builder.md) 和 [Active Record](db-active-record.md) +> 等更高抽象层的许多地方,您经常会指定一组将被转换为 SQL 的值。 +> 在这些地方,参数绑定是由 Yii 内部完成的,因此不需要手动指定参数。 -### 执行非查询语句 -上面部分中介绍的 `queryXyz()` 方法都处理的是从数据库返回数据的查询语句。 -对于那些不取回数据的语句, -你应该调用的是 [[yii\db\Command::execute()]] 方法。 -例如, +### 执行非查询语句(Executing Non-SELECT Queries) + +上面部分中介绍的 `queryXyz()` 方法都处理的是从数据库返回数据的查询语句。对于那些不取回数据的语句, +你应该调用的是 [[yii\db\Command::execute()]] 方法。例如, ```php Yii::$app->db->createCommand('UPDATE post SET status=1 WHERE id=1') @@ -235,14 +227,9 @@ Yii::$app->db->createCommand('UPDATE post SET status=1 WHERE id=1') [[yii\db\Command::execute()]] 方法返回执行 SQL 所影响到的行数。 -对于 INSERT, UPDATE 和 DELETE 语句, -不再需要写纯SQL语句了, -你可以直接调用 [[yii\db\Command::insert()|insert()]]、 -[[yii\db\Command::update()|update()]]、 -[[yii\db\Command::delete()|delete()]], -来构建相应的 SQL 语句。 -这些方法将正确地引用表和列名称以及绑定参数值。 -例如, +对于 INSERT, UPDATE 和 DELETE 语句,不再需要写纯SQL语句了, +你可以直接调用 [[yii\db\Command::insert()|insert()]]、[[yii\db\Command::update()|update()]]、[[yii\db\Command::delete()|delete()]], +来构建相应的 SQL 语句。这些方法将正确地引用表和列名称以及绑定参数值。例如, ```php // INSERT (table name, column values) @@ -270,23 +257,36 @@ Yii::$app->db->createCommand()->batchInsert('user', ['name', 'age'], [ ])->execute(); ``` +另一个有用的方法是 [[yii\db\Command::upsert()|upsert()]]。Upsert 是一种原子操作,如果它们不存在(匹配唯一约束),则将行插入到数据库表中, +或者在它们执行时更新它们: + +```php +Yii::$app->db->createCommand()->upsert('pages', [ + 'name' => 'Front page', + 'url' => 'http://example.com/', // url is unique + 'visits' => 0, +], [ + 'visits' => new \yii\db\Expression('visits + 1'), +], $params)->execute(); +``` + +上面的代码将插入一个新的页面记录或自动增加访问计数器。 + 请注意,上述的方法只是构建出语句, 你总是需要调用 [[yii\db\Command::execute()|execute()]] 来真正地执行它们。 -## 引用表和列名称 +## 引用表和列名称(Quoting Table and Column Names) -当写与数据库无关的代码时, -正确地引用表和列名称总是一件头疼的事, -因为不同的数据库有不同的名称引用规则, -为了克服这个问题, +当写与数据库无关的代码时,正确地引用表和列名称总是一件头疼的事, +因为不同的数据库有不同的名称引用规则,为了克服这个问题, 你可以使用下面由 Yii 提出的引用语法。 - * `[[column name]]`: 使用两对方括号来将列名括起来; * `{{table name}}`: 使用两对大括号来将表名括起来。 -Yii DAO 将自动地根据数据库的具体语法来将这些结构转化为对应的被引用的列或者表名称。 +Yii DAO 将自动地根据数据库的具体语法来将这些结构转化为对应的 +被引用的列或者表名称。 例如, ```php @@ -296,7 +296,7 @@ $count = Yii::$app->db->createCommand("SELECT COUNT([[id]]) FROM {{employee}}") ``` -### 使用表前缀 +### 使用表前缀(Using Table Prefix) 如果你的数据库表名大多都拥有一个共同的前缀, 你可以使用 Yii DAO 所提供的表前缀功能。 @@ -316,10 +316,8 @@ return [ ]; ``` -接着在你的代码中, -当你需要涉及到一张表名中包含该前缀的表时, -应使用语法 `{{%table_name}}`。 -百分号将被自动地替换为你在配置 DB 组件时指定的表前缀。 +接着在你的代码中,当你需要涉及到一张表名中包含该前缀的表时, +应使用语法 `{{%table_name}}`。百分号将被自动地替换为你在配置 DB 组件时指定的表前缀。 例如, ```php @@ -328,14 +326,10 @@ $count = Yii::$app->db->createCommand("SELECT COUNT([[id]]) FROM {{%employee}}") ->queryScalar(); ``` -### 预处理语句 -为安全传递查询参数可以使用预处理语句,首先应当使用 `:placeholder` 占位,再将变量绑定到对应占位符: +## 执行事务(Performing Transactions) -## 执行事务 - -当顺序地执行多个相关的语句时, -你或许需要将它们包在一个事务中来保证数据库的完整性和一致性。 +当顺序地执行多个相关的语句时, 你或许需要将它们包在一个事务中来保证数据库的完整性和一致性。 如果这些语句中的任何一个失败了, 数据库将回滚到这些语句执行前的状态。 @@ -349,24 +343,22 @@ Yii::$app->db->transaction(function($db) { }); ``` -上述代码等价于下面的代码, -但是下面的代码给予了你对于错误处理代码的更多掌控: +上述代码等价于下面的代码,但是下面的代码给予了你对于错误处理代码的更多掌控: ```php $db = Yii::$app->db; $transaction = $db->beginTransaction(); - try { $db->createCommand($sql1)->execute(); $db->createCommand($sql2)->execute(); // ... executing other SQL statements ... $transaction->commit(); - } catch(\Exception $e) { - $transaction->rollBack(); - + throw $e; +} catch(\Throwable $e) { + $transaction->rollBack(); throw $e; } ``` @@ -384,13 +376,10 @@ try { 就好像我们没有捕获它一样, 因此正常的错误处理程序将处理它。 -### 指定隔离级别 +### 指定隔离级别(Specifying Isolation Levels) -Yii 也支持为你的事务设置[隔离级别]。 -默认情况下, -当我们开启一个新事务, -它将使用你的数据库所设定的隔离级别。 -你也可以向下面这样重载默认的隔离级别, +Yii 也支持为你的事务设置[隔离级别]。默认情况下,当我们开启一个新事务, +它将使用你的数据库所设定的隔离级别。你也可以向下面这样重载默认的隔离级别, ```php $isolationLevel = \yii\db\Transaction::REPEATABLE_READ; @@ -411,38 +400,28 @@ Yii 为四个最常用的隔离级别提供了常量: - [[\yii\db\Transaction::REPEATABLE_READ]] - 避免了脏读和不可重复读。 - [[\yii\db\Transaction::SERIALIZABLE]] - 最强的隔离级别, 避免了上述所有的问题。 -> 注意: 你使用的数据库必须支持 `Savepoints` 才能正确地执行,以上代码在所有关系数据中都可以执行,但是只有支持 `Savepoints` 才能保证安全性。 - -Yii 也支持为事务设置隔离级别 `isolation levels`,当执行事务时会使用数据库默认的隔离级别,你也可以为事务指定隔离级别. -Yii 提供了以下常量作为常用的隔离级别 - -除了使用上述的常量来指定隔离级别, -你还可以使用你的数据库所支持的具有有效语法的字符串。 -比如,在 PostgreSQL 中, -你可以使用 `SERIALIZABLE READ ONLY DEFERRABLE`。 +除了使用上述的常量来指定隔离级别,你还可以使用你的数据库所支持的具有有效语法的字符串。 +比如,在 PostgreSQL 中,你可以使用 `SERIALIZABLE READ ONLY DEFERRABLE`。 请注意,一些数据库只允许为整个连接设置隔离级别, -即使你之后什么也没指定, -后来的事务都将获得与之前相同的隔离级别。 +即使你之后什么也没指定,后来的事务都将获得与之前相同的隔离级别。 使用此功能时,你需要为所有的事务明确地设置隔离级别来避免冲突的设置。 -在本文写作之时, -只有 MSSQL 和 SQLite 受这些限制的影响。 +在本文写作之时,只有 MSSQL 和 SQLite 受这些限制的影响。 -> Note: SQLite 只支持两种隔离级别, -所以你只能使用 `READ UNCOMMITTED` 和 `SERIALIZABLE`。 +> Note: SQLite 只支持两种隔离级别,所以你只能使用 `READ UNCOMMITTED` 和 `SERIALIZABLE`。 使用其他级别将导致异常的抛出。 - > Note: PostgreSQL 不支持在事务开启前设定隔离级别, 因此,你不能在开启事务时直接指定隔离级别。 你必须在事务开始后再调用 [[yii\db\Transaction::setIsolationLevel()]]。 [隔离级别]: http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels -### 嵌套事务 -如果你的数据库支持保存点, -你可以像下面这样嵌套多个事务: +### 嵌套事务(Nesting Transactions) + +如果你的数据库支持保存点,你可以像下面这样嵌套多个事务: + ```php Yii::$app->db->transaction(function ($db) { // outer transaction @@ -468,26 +447,31 @@ try { } catch (\Exception $e) { $innerTransaction->rollBack(); throw $e; + } catch (\Throwable $e) { + $innerTransaction->rollBack(); + throw $e; } $outerTransaction->commit(); } catch (\Exception $e) { $outerTransaction->rollBack(); throw $e; +} catch (\Throwable $e) { + $outerTransaction->rollBack(); + throw $e; } ``` -## 复制和读写分离 +## 复制和读写分离(Replication and Read-Write Splitting) 许多数据库支持[数据库复制](http://en.wikipedia.org/wiki/Replication_(computing)#Database_replication)来获得更好的数据库可用性, -以及更快的服务器响应时间。 -通过数据库复制功能, -数据从所谓的主服务器被复制到从服务器。 -所有的写和更新必须发生在主服务器上, +以及更快的服务器响应时间。通过数据库复制功能, +数据从所谓的主服务器被复制到从服务器。所有的写和更新必须发生在主服务器上, 而读可以发生在从服务器上。 -为了利用数据库复制并且完成读写分离,你可以按照下面的方法来配置 [[yii\db\Connection]] 组件: +为了利用数据库复制并且完成读写分离, +你可以按照下面的方法来配置 [[yii\db\Connection]] 组件: ```php [ @@ -519,10 +503,8 @@ try { ``` 上述的配置指定了一主多从的设置。 -这些从库其中之一将被建立起连接并执行读操作, -而主库将被用来执行写操作。 -这样的读写分离将通过上述配置自动地完成。 -比如, +这些从库其中之一将被建立起连接并执行读操作,而主库将被用来执行写操作。 +这样的读写分离将通过上述配置自动地完成。比如, ```php // 使用上述配置来创建一个 Connection 实例 @@ -540,24 +522,20 @@ Yii::$app->db->createCommand("UPDATE user SET username='demo' WHERE id=1")->exec 你可以通过 `Yii::$app->db->slave` 来获取当前有效的从库连接。 `Connection` 组件支持从库间的负载均衡和失效备援, -当第一次执行读操作时, -`Connection` 组件将随机地挑选出一个从库并尝试与之建立连接, -如果这个从库被发现为”挂掉的“, -将尝试连接另一个从库。 -如果没有一个从库是连接得上的, -那么将试着连接到主库上。 +当第一次执行读操作时,`Connection` 组件将随机地挑选出一个从库并尝试与之建立连接, +如果这个从库被发现为”挂掉的“,将尝试连接另一个从库。 +如果没有一个从库是连接得上的,那么将试着连接到主库上。 通过配置 [[yii\db\Connection::serverStatusCache|server status cache]], -一个“挂掉的”服务器将会被记住, -因此,在一个 yii\db\Connection::serverRetryInterval 内将不再试着连接该服务器。 +一个“挂掉的”服务器将会被记住,因此,在一个 yii\db\Connection::serverRetryInterval 内将不再试着连接该服务器。 > Info: 在上面的配置中, - 每个从库都共同地指定了 10 秒的连接超时时间, - 这意味着,如果一个从库在 10 秒内不能被连接上, - 它将被视为“挂掉的”。 + 每个从库都共同地指定了 10 秒的连接超时时间,这意味着,如果一个从库在 10 秒内不能被连接上,它将被视为“挂掉的”。 你可以根据你的实际环境来调整该参数。 + 你也可以配置多主多从。例如, + ```php [ 'class' => 'yii\db\Connection', @@ -599,19 +577,16 @@ Yii::$app->db->createCommand("UPDATE user SET username='demo' WHERE id=1")->exec ``` 上述配置指定了两个主库和两个从库。 -`Connection` 组件在主库之间, -也支持如从库间般的负载均衡和失效备援。 -唯一的差别是, -如果没有主库可用,将抛出一个异常。 +`Connection` 组件在主库之间,也支持如从库间般的负载均衡和失效备援。 +唯一的差别是,如果没有主库可用,将抛出一个异常。 > Note: 当你使用 [[yii\db\Connection::masters|masters]] 属性来配置一个或多个主库时, - 所有其他指定数据库连接的属性 (例如 `dsn`, `username`, `password`) 与 `Connection` 对象本身将被忽略。 + 所有其他指定数据库连接的属性 (例如 `dsn`, `username`, `password`) + 与 `Connection` 对象本身将被忽略。 -默认情况下, -事务使用主库连接, -一个事务内, -所有的数据库操作都将使用主库连接, -例如, + +默认情况下,事务使用主库连接, +一个事务内,所有的数据库操作都将使用主库连接,例如, ```php $db = Yii::$app->db; @@ -627,6 +602,9 @@ try { } catch(\Exception $e) { $transaction->rollBack(); throw $e; +} catch(\Throwable $e) { + $transaction->rollBack(); + throw $e; } ``` @@ -635,6 +613,7 @@ try { ```php $transaction = Yii::$app->db->slave->beginTransaction(); ``` + 有时,你或许想要强制使用主库来执行读查询。 这可以通过 `useMaster()` 方法来完成: @@ -642,11 +621,12 @@ $transaction = Yii::$app->db->slave->beginTransaction(); $rows = Yii::$app->db->useMaster(function ($db) { return $db->createCommand('SELECT * FROM user LIMIT 10')->queryAll(); }); -你也可以明确地将 `Yii::$app->db->enableSlaves` 设置为 false 来将所有的读操作指向主库连接。 ``` +你也可以明确地将 `Yii::$app->db->enableSlaves` 设置为 false 来将所有的读操作指向主库连接。 -## 操纵数据库模式 + +## 操纵数据库模式(Working with Database Schema) Yii DAO 提供了一套完整的方法来让你操纵数据库模式, 如创建表、从表中删除一列,等等。这些方法罗列如下: @@ -677,21 +657,13 @@ Yii::$app->db->createCommand()->createTable('post', [ ]); ``` -更多信息请参考[[yii\db\Schema]] - -### 修改模式 - 上面的数组描述要创建的列的名称和类型。 -对于列的类型, -Yii 提供了一套抽象数据类型来允许你定义出数据库无关的模式。 -这些将根据表所在数据库的种类, -被转换为特定的类型定义。 +对于列的类型, Yii 提供了一套抽象数据类型来允许你定义出数据库无关的模式。 +这些将根据表所在数据库的种类,被转换为特定的类型定义。 请参考 [[yii\db\Command::createTable()|createTable()]]-method 的 API 文档来获取更多信息。 除了改变数据库模式, -你也可以通过 DB Connection 的 [[yii\db\Connection::getTableSchema()|getTableSchema()]] 方法来检索某张表的定义信息。 -例如, - +你也可以通过 DB Connection 的 [[yii\db\Connection::getTableSchema()|getTableSchema()]] 方法来检索某张表的定义信息。例如, ```php $table = Yii::$app->db->getTableSchema('post'); @@ -700,4 +672,3 @@ $table = Yii::$app->db->getTableSchema('post'); 该方法返回一个 [[yii\db\TableSchema]] 对象, 它包含了表中的列、主键、外键,等等的信息。 所有的这些信息主要被 [query builder](db-query-builder.md) 和 [active record](db-active-record.md) 所使用,来帮助你写出数据库无关的代码。 - diff --git a/docs/guide-zh-CN/helper-array.md b/docs/guide-zh-CN/helper-array.md index bef0999..24cbc95 100644 --- a/docs/guide-zh-CN/helper-array.md +++ b/docs/guide-zh-CN/helper-array.md @@ -1,14 +1,14 @@ -数组助手类 -=========== +数组助手类(ArrayHelper) +====================== 除了[PHP中丰富的数组函数集](http://php.net/manual/zh/book.array.php), Yii 数组助手类提供了额外的静态方法,让你更高效地处理数组。 -## 获取值 +## 获取值(Getting Values) 用原生PHP从一个对象、数组、或者包含这两者的一个复杂数据结构中获取数据是非常繁琐的。 -你首先得使用`isset` 检查 key 是否存在, 然后如果存在你就获取它,如果不存在, +你首先得使用 `isset` 检查 key 是否存在, 然后如果存在你就获取它,如果不存在, 则提供一个默认返回值: ```php @@ -53,18 +53,69 @@ $fullName = ArrayHelper::getValue($user, function ($user, $defaultValue) { $username = ArrayHelper::getValue($comment, 'user.username', 'Unknown'); ``` -对于取到值后想要立即从数组中删除的情况,你可以使用 `remove` 方法: + +## 设定值(Setting values) + +```php +$array = [ + 'key' => [ + 'in' => ['k' => 'value'] + ] +]; + +ArrayHelper::setValue($array, 'key.in', ['arr' => 'val']); +// 在 `$array` 中写入值的路径可以被指定为一个数组 +ArrayHelper::setValue($array, ['key', 'in'], ['arr' => 'val']); +``` + +结果,`$array['key']['in']` 的初始值将被新值覆盖 + +```php +[ + 'key' => [ + 'in' => ['arr' => 'val'] + ] +] +``` + +如果路径包含一个不存在的键,它将被创建 + +```php +// 如果 `$array['key']['in']['arr0']` 不为空,则该值将被添加到数组中 +ArrayHelper::setValue($array, 'key.in.arr0.arr1', 'val'); + +// 如果你想完全覆盖值 `$array['key']['in']['arr0']` +ArrayHelper::setValue($array, 'key.in.arr0', ['arr1' => 'val']); +``` + +结果将是 + +```php +[ + 'key' => [ + 'in' => [ + 'k' => 'value', + 'arr0' => ['arr1' => 'val'] + ] + ] +] +``` + + +## 从数组中获取值(Take a value from an array) + +如果你想获得一个值,然后立即从数组中删除它,你可以使用 `remove` 方法: ```php $array = ['type' => 'A', 'options' => [1, 2]]; $type = ArrayHelper::remove($array, 'type'); ``` -执行了上述代码之后, `$array` 将包含 `['options' => [1, 2]]` 并且 `$type` 将会是 `A` 。 -注意和 `getValue` 方法不同的是,`remove` 方法只支持简单键名。 +执行代码后,`$array` 将包含 `['options' => [1, 2]]` 且 `$type` 将包含 `A`。 +请注意,与 `getValue` 方法不同,`remove` 仅支持简单的键名称。 -## 检查键名的存在 +## 检查键名的存在(Checking Existence of Keys) `ArrayHelper::keyExists` 工作原理和[array_key_exists](http://php.net/manual/en/function.array-key-exists.php)差不多,除了 它还可支持大小写不敏感的键名比较,比如: @@ -83,7 +134,7 @@ if (!ArrayHelper::keyExists('username', $data1, false) || !ArrayHelper::keyExist } ``` -## 检索列 +## 检索列(Retrieving Columns) 通常你要从多行数据或者多个对象构成的数组中获取某列的值,一个普通的例子是获取id值列表。 @@ -107,20 +158,20 @@ $result = ArrayHelper::getColumn($array, function ($element) { ``` -## 重建数组索引 +## 重建数组索引(Re-indexing Arrays) 按一个指定的键名重新索引一个数组,可以用 `index` 方法。输入的数组应该是多维数组或者是一个对象数组。 键名(译者注:第二个参数)可以是子数组的键名、对象的属性名, 也可以是一个返回给定元素数组键值的匿名函数。 -The `$groups` attribute is an array of keys, that will be used to group the input array into one or more sub-arrays -based on keys specified. +`$groups` 属性是一个键数组, +它将根据指定的键将输入数组分组为一个或多个子数组。 -If the `$key` attribute or its value for the particular element is null and `$groups` is not defined, the array -element will be discarded. Otherwise, if `$groups` is specified, array element will be added to the result array -without any key. +如果 `$key` 属性或其特定元素的值为 null,并且未定义 `$groups`, +则数组元素将被丢弃。否则,如果指定了 `$groups`, +则数组元素将被添加到没有任何键的结果数组中。 -For example: +例如: ```php $array = [ @@ -131,17 +182,17 @@ $array = [ $result = ArrayHelper::index($array, 'id'); ``` -The result will be an associative array, where the key is the value of `id` attribute +结果将是一个关联数组,其中键是 `id` 属性的值 ```php [ '123' => ['id' => '123', 'data' => 'abc', 'device' => 'laptop'], '345' => ['id' => '345', 'data' => 'hgi', 'device' => 'smartphone'] - // The second element of an original array is overwritten by the last element because of the same id + // 原始数组的第二个元素由于相同的 ID 而被最后一个元素覆盖 ] ``` -Anonymous function, passed as a `$key`, gives the same result. +匿名函数作为 `$key` 传递,给出了相同的结果。 ```php $result = ArrayHelper::index($array, function ($element) { @@ -149,13 +200,13 @@ $result = ArrayHelper::index($array, function ($element) { }); ``` -Passing `id` as a third argument will group `$array` by `id`: +传递 `id` 作为第三个参数将 `id` 分配给 `$ array`: ```php $result = ArrayHelper::index($array, null, 'id'); ``` -The result will be a multidimensional array grouped by `id` on the first level and not indexed on the second level: +结果将是一个多维数组,它由第一级的 `id` 分组,并且不在第二级索引: ```php [ @@ -169,7 +220,7 @@ The result will be a multidimensional array grouped by `id` on the first level a ] ``` -An anonymous function can be used in the grouping array as well: +匿名函数也可用于分组数组中: ```php $result = ArrayHelper::index($array, 'data', [function ($element) { @@ -177,8 +228,8 @@ $result = ArrayHelper::index($array, 'data', [function ($element) { }, 'device']); ``` -The result will be a multidimensional array grouped by `id` on the first level, by `device` on the second level and -indexed by `data` on the third level: +结果将是一个多维数组,由第一级的 `id` 分组,第二级的 `device` 和第三级的 +`data` 索引: ```php [ @@ -198,10 +249,10 @@ indexed by `data` on the third level: ] ``` -## 建立哈希表 +## 建立哈希表(Building Maps) -为了从一个多维数组或者一个对象数组中建立一个映射表(键值对),你可以使用 -`map`方法.`$from` 和 `$to` 参数分别指定了欲构建的映射表的键名和属性名。 +为了从一个多维数组或者一个对象数组中建立一个映射表(键值对),你可以使用 +`map` 方法。`$from` 和 `$to` 参数分别指定了欲构建的映射表的键名和属性名。 根据需要,你可以按照一个分组字段 `$group` 将映射表进行分组,例如, ```php @@ -233,7 +284,7 @@ $result = ArrayHelper::map($array, 'id', 'name', 'class'); ``` -## 多维排序 +## 多维排序(Multidimensional Sorting) `multisort` 方法可用来对嵌套数组或者对象数组进行排序,可按一到多个键名排序,比如, @@ -273,7 +324,7 @@ ArrayHelper::multisort($data, function($item) { [sort()](http://php.net/manual/zh/function.sort.php) 函数时传递的值一样。 -## 检测数组类型 +## 检测数组类型(Detecting Array Types) 想知道一个数组是索引数组还是联合数组很方便,这有个例子: @@ -288,7 +339,7 @@ echo ArrayHelper::isAssociative($associative); ``` -## HTML 编码和解码值 +## HTML 编码和解码值(HTML Encoding and Decoding Values) 为了将字符串数组中的特殊字符做 HTML 编解码,你可以使用下列方法: @@ -301,30 +352,77 @@ $decoded = ArrayHelper::htmlDecode($data); 编码将默认使用应用程序的字符集,你可以通过第三个参数指定该字符集。 -## 合并数组 +## 合并数组(Merging Arrays) + +您可以使用 [[yii\helpers\ArrayHelper::merge()|ArrayHelper::merge()]] 将两个或多个数组合并成一个递归的数组。 +如果每个数组都有一个具有相同字符串键值的元素,则后者将覆盖前者 +(不同于 [array_merge_recursive()](http://php.net/manual/en/function.array-merge-recursive.php))。 +如果两个数组都有一个数组类型的元素并且具有相同的键,则将执行递归合并。 +对于整数键的元素,来自后一个数组的元素将被附加到前一个数组。 +您可以使用 [[yii\helpers\UnsetArrayValue]] 对象来取消前一个数组的值或 +[[yii\helpers\ReplaceArrayValue]] 以强制替换先前的值而不是递归合并。 + +例如: ```php - /** - * 将两个或者多个数组递归式的合并为一个数组。 - * 如果每个数组有一个元素的键名相同, - * 那么后面元素的将覆盖前面的元素(不同于 array_merge_recursive)。 - * 如果两个数组都有相同键名的数组元素(译者注:嵌套数组) - * 则将引发递归合并。 - * 对数值型键名的元素,后面数组中的这些元素会被追加到前面数组中。 - * @param array $a 被合并的数组 - * @param array $b 合并的数组,你可以在第三、第四个 - * 参数中指定另外的合并数组,等等 - * @return 合并的结果数组 (原始数组不会被改变) - */ - public static function merge($a, $b) +$array1 = [ + 'name' => 'Yii', + 'version' => '1.1', + 'ids' => [ + 1, + ], + 'validDomains' => [ + 'example.com', + 'www.example.com', + ], + 'emails' => [ + 'admin' => 'admin@example.com', + 'dev' => 'dev@example.com', + ], +]; + +$array2 = [ + 'version' => '2.0', + 'ids' => [ + 2, + ], + 'validDomains' => new \yii\helpers\ReplaceArrayValue([ + 'yiiframework.com', + 'www.yiiframework.com', + ]), + 'emails' => [ + 'dev' => new \yii\helpers\UnsetArrayValue(), + ], +]; + +$result = ArrayHelper::merge($array1, $array2); ``` +结果将是: + +```php +[ + 'name' => 'Yii', + 'version' => '2.0', + 'ids' => [ + 1, + 2, + ], + 'validDomains' => [ + 'yiiframework.com', + 'www.yiiframework.com', + ], + 'emails' => [ + 'admin' => 'admin@example.com', + ], +] +``` -## 对象转换为数组 +## 对象转换为数组(Converting Objects to Arrays) -你经常要将一个对象或者对象的数组转换成一个数组,常见的情形是,为了通过REST API提供数据数组(或其他使用方式), -将AR模型(活动记录模型)转换成数组。如下代码可完成这个工作: +你经常要将一个对象或者对象的数组转换成一个数组,常见的情形是,为了通过 REST API 提供数据数组(或其他使用方式), +将 AR 模型(活动记录模型)转换成数组。如下代码可完成这个工作: ```php $posts = Post::find()->limit(10)->all(); @@ -344,7 +442,7 @@ $data = ArrayHelper::toArray($posts, [ 第一个参数包含我们想要转换的数据,在本例中,我们要转换一个叫 `Post` 的 AR 模型。 -第二个参数是每个类的转换映射表,我们在此设置了一个`Post` 模型的映射。 +第二个参数是每个类的转换映射表,我们在此设置了一个 `Post` 模型的映射。 每个映射数组包含一组的映射,每个映射可以是: - 一个要包含的照原样的字段名(和类中属性的名称一致); @@ -363,16 +461,16 @@ $data = ArrayHelper::toArray($posts, [ ] ``` -也可以在一个特定的类中实现[[yii\base\Arrayable|Arrayable]]接口, +也可以在一个特定的类中实现 [[yii\base\Arrayable|Arrayable]] 接口, 从而为其对象提供默认的转换成数组的方法。 -## Testing against Arrays +## 测试阵列(Testing against Arrays) -Often you need to check if an element is in an array or a set of elements is a subset of another. -While PHP offers `in_array()`, this does not support subsets or `\Traversable` objects. +通常你需要检查一个元素是否在数组中,或者一组元素是另一个元素的子集。 +虽然PHP提供 `in_array()`,这不支持子集或 `\Traversable` 对象。 -To aid these kinds of tests, [[yii\base\ArrayHelper]] provides [[yii\base\ArrayHelper::isIn()|isIn()]] -and [[yii\base\ArrayHelper::isSubset()|isSubset()]] with the same signature as [[in_array()]]. +为了支持这些测试,[[yii\base\ArrayHelper]] 提供了 [[yii\base\ArrayHelper::isIn()|isIn()]] +和 [[yii\base\ArrayHelper::isSubset()|isSubset()]] 与 [[in_array()]] 签名相同。 ```php // true diff --git a/docs/guide-zh-CN/helper-html.md b/docs/guide-zh-CN/helper-html.md index e61374d..5cc7aee 100644 --- a/docs/guide-zh-CN/helper-html.md +++ b/docs/guide-zh-CN/helper-html.md @@ -1,5 +1,5 @@ -Html 帮助类 -=========== +Html 帮助类(Html helper) +======================= 任何一个 web 应用程序会生成很多 HTMl 超文本标记。如果超文本标记是静态的, 那么[将 PHP 和 HTML 混合在一个文件里](http://php.net/manual/en/language.basic-syntax.phpmode.php) @@ -10,13 +10,13 @@ Yii 通过 HTML 帮助类来提供生成超文本标记的方法。这个帮助 没有必要把所有的超文本标记都用 HTML 辅助类来生成。 -## 基础 +## 基础(Basics) 由于通过字符串连接来生成动态的 HTML 会很容易变得凌乱, Yii 提供了一系列的静态方法来操作标签配置并基于这些配置来创建对应的标签。 -### 生成标签 +### 生成标签(Generating Tags) 生成一个标签的代码类似如下: @@ -56,7 +56,7 @@ Yii 提供了一系列的静态方法来操作标签配置并基于这些配置 生成 `params='{"id":1,"name":"yii"}'`。 -### 生成 CSS 类和样式 +### 生成 CSS 类和样式(Forming CSS Classes and Styles) 当开始构造一个 HTML 标签的属性时,我们经常需要对默认的属性进行修改。 为了添加或者删除 CSS 类,你可以使用如下代码: @@ -84,7 +84,7 @@ echo Html::tag('div', 'Save', $options); // renders '