Commit c8ee057e by Qiang Xue

Merge pull request #4102 from yii2-chinesization/master

Chinese Translation for this week
parents d7623a39 ec801004
组件(Component)
==========
组件是 Yii 应用的主要基石之一。组件是 [[yii\base\Component]] 类或其子类的实例。三个它能提供,其他类不能的主要功能有:
* [属性(Property)](concept-properties.md)
* [事件(Event)](concept-events.md)
* [行为(Behavior)](concept-behaviors.md)
或单独使用,或彼此配合,总之这些功能的应用让 Yii 的类变得更加灵活和易用。就拿一个叫 [[yii\jui\DatePicker|日期选择器]]
的小部件来举例吧,这是个方便你在 [视图](structure-view.md) 中生成一个交互式日期选择器的 UI 组件,你们自己看这样的调用方式是不是很屌:
```php
use yii\jui\DatePicker;
echo DatePicker::widget([
'language' => 'zh-CN',
'name' => 'country',
'clientOptions' => [
'dateFormat' => 'yy-mm-dd',
],
]);
```
正因为这个小部件继承自 [[yii\base\Component]],所以它的各项属性改写起来就会很容易……
虽然组件非常屌爆,但是他们比常规的对象(Object)要稍微重量级一点点,因为他们要使用额外的内存和 CPU 时间来支持这些功能,尤其是
[事件](concept-events.md)[行为](concept-behaviors.md) 这俩货。如果你的组件不需要这两项功能,你可以考虑继承 [[yii\base\Object]]
而不是 [[yii\base\Component]]。这样一来,你的组件就可以像普通 PHP 对象一样高效了。同时,它还依旧支持[属性(Property)](concept-properties.md)功能!
当你继承 [[yii\base\Component]] 或 [[yii\base\Object]] 时,我们推荐你使用如下的编码风格:
- 若你需要重写构造器(Constructor),指定一个 `$config` 参数,作为构造器的 *最后一个* 参数,然后把它传递给父类的构造器。(译者注:`parent::__construct($config = [])`,用于把属性配置信息传递回父类。可选参数放最后是 PSR 的规范之一)
- 永远在你重写的构造器 *结尾处* 调用一下父类的构造器。
- 如果你重写了 [[yii\base\Object::init()]] 方法,请确保你在 `init` 方法的 *开头处* 调用了父类的 `init` 方法。
例子如下:
```php
namespace yii\components\MyClass;
use yii\base\Object;
class MyClass extends Object
{
public $prop1;
public $prop2;
public function __construct($param1, $param2, $config = [])
{
// ... 配置生效前的初始化过程
parent::__construct($config);
}
public function init()
{
parent::init();
// ... 配置生效后的初始化过程
}
}
```
另外,为了让你的组件可以在创建实例时[能被正确配置](concept-configurations.md),请遵照以下操作流程。举例:
```php
$component = new MyClass(1, 2, ['prop1' => 3, 'prop2' => 4]);
// 方法二:
$component = \Yii::createObject([
'class' => MyClass::className(),
'prop1' => 3,
'prop2' => 4,
], [1, 2]);
```
> 补充:虽然调用 [[Yii::createObject()]] 的方法看起来更加复杂,但是这主要是因为它更加灵活强大,这货是基于高大上的[依赖注入容器](concept-di-container.md)的一种实现。
每个 [[yii\base\Object]] 类的生命周期是这样度过的:
1. 构造器内的预初始化过程。你可以在这儿给各属性设置缺省值。
2. 通过 `$config` 配置对象。配置的过程可能会覆盖掉先前在构造器内设置的默认值。
3.[[yii\base\Object::init()|init()]] 方法内进行初始化的收尾工作。你可以通过重写此方法,进行一些良品检验呀,属性的标准化呀,之类的事情。
4. 对象方法调用。
前三步都是在对象的构造器内发生的。这意味着一旦你获得了一个对象实例,那么它已经初始化为了一个妥妥的状态,放心大胆的用吧。
属性(Property)
==========
PHP 中,类的成员变量也被称为 *属性(properties)*。它们是类定义的一部分,并用来表现一个实例的状态(比如,用于区分某类的各个实例)。在具体实践中,你会常常想用一个稍微特殊些的方法实现属性的读写。例如,如果你想要去掉赋值给 `label` 属性的字符串前后的空格,就可以用以下代码实现:
```php
$object->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` 的属性:
```php
namespace app\components;
use yii\base\Object;
class Foo extend Object
{
private $_label;
public function getLabel()
{
return $this->_label;
}
public function setLabel($value)
{
$this->_label = trim($value);
}
}
```
(详细解释:getter 和 setter 方法创建了一个名为 `label` 的属性,在这个例子里,它指向一个私有的内部属性 `_label`。译者注:习惯上一般把私有属性都定义为 `_` 下划线开头的名字。)
getters/setters 定义的属性能像类成员变量那样使用。两者主要的区别是:当这种属性被读取时,对应的 getter
方法将被调用;而当属性被赋值时,对应的 setter 方法就调用。如:
```php
// 等效于 $label = $object->getLabel();
$label = $object->label;
// 等效于 $object->setLabel('abc');
$object->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()` 方法,而无须接触任何其它代码。
\ No newline at end of file
服务定位器(Service Locator)
===============
服务定位器是一个了解如何提供各种应用所需的服务(或组件)的对象。在一个服务定位器中,每一个组件都只有一个单独的实例,并通过
ID 唯一地标识。用这个 ID 就能从服务定位器中得到这个组件。
在 Yii 中,服务定位器只是 [[yii\di\ServiceLocator]] 或其子类的一个实例。
最最常用的服务定位器一般是 *application(应用)* 对象,可以通过 `\Yii::$app` 访问。它所提供的服务被称为
*application components(应用组件)*,比如:`request``response``urlManager`(分别是请求、响应、Url 管理器)组件。你可以通过服务定位器所提供的功能,非常容易地配置这些组件,或甚至是用你自己的实现替换掉他们。
除了 Application 对象,每一个模块对象本身也是一个服务定位器。
要使用一个服务定位器,第一步是要注册相关组件。组件可以通过 [[yii\di\ServiceLocator::set()]]
方法进行注册。以下的方法展示了注册组件的不同方法:
```php
use yii\di\ServiceLocator;
use yii\caching\FileCache;
$locator = new ServiceLocator;
// 通过一个可用于创建该组件的类名,注册 "cache" (缓存)组件。
$locator->set('cache', 'yii\caching\ApcCache');
// 通过一个可用于创建该组件的配置数组,注册 "db" (数据库)组件。
$locator->set('db', [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=demo',
'username' => 'root',
'password' => '',
]);
// 通过一个能返回该组件的匿名函数,注册 "search" 组件。
$locator->set('search', function () {
return new app\components\SolrService;
});
// 用组件注册 "pageCache" 组件
$locator->set('pageCache', new FileCache);
```
一旦组件被注册成功,你可以任选以下两种方式之一,通过它的 ID 访问它:
```php
$cache = $locator->get('cache');
// 或者
$cache = $locator->cache;
```
如上文所示, [[yii\di\ServiceLocator]] 允许你通过组件 ID 像访问一个属性值那样访问一个组件。当你第一次访问某组件时,
[[yii\di\ServiceLocator]] 会通过该组件的注册信息创建一个该组件的实例,并返回它。之后,如果再次访问,则服务定位器会返回同一个实例。
你可以通过 [[yii\di\ServiceLocator::has()]] 检查某组件 ID 是否被注册。
若你用一个无效的 ID 调用 [[yii\di\ServiceLocator::get()]],则会抛出一个异常。
因为服务定位器,经常会在创建时附带[配置信息](concept-configurations.md),因此我们提供了一个可写的属性,名为
[[yii\di\ServiceLocator::setComponents()|components]],这样就可以配置该属性,或一次性注册多个组件。下面的代码展示了如何用一个配置数组,配置一个应用并注册
"db","cache" 和 "search" 三个组件:
```php
return [
// ...
'components' => [
'db' => [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=demo',
'username' => 'root',
'password' => '',
],
'cache' => 'yii\caching\ApcCache',
'search' => function () {
return new app\components\SolrService;
},
],
];
```
使用数据库
======================
本小节将介绍如何如何创建一个从数据表 `country` 中获取国家数据并显示出来的页面。为了实现这个目标,你将会配置一个数据库连接,创建一个[活动记录](db-active-record.md)类,并且创建一个[操作](structure-controllers.md)及一个[视图](structure-views.md)
贯穿整个小节,你将会学到:
* 配置一个数据库连接
* 定义一个活动记录类
* 使用活动记录从数据库中查询数据
* 以分页方式在视图中显示数据
请注意,为了掌握本小节你应该具备最基本的数据库知识和使用经验。尤其是你应该知道如何创建数据库,如何通过数据库终端执行 SQL 语句。
准备数据库 <a name="preparing-database"></a>
--------------------
首先创建一个名为 `yii2basic` 的数据库,你的应用将从这个数据库中获取数据。你可以创建 SQLite,MySQL,PostregSQL,MSSQL 或 Oracle 数据库,Yii 内置多种数据库支持。简单起见后面的内容将以 MySQL 为例做演示。
然后在数据库中创建一个名为 `country` 的表并插入简单的数据。可以执行下面的语句:
```sql
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`,表中有十行数据。
配置数据库连接 <a name="configuring-db-connection"></a>
---------------------------
开始之前,请确保你已经安装了 PHP [PDO](http://www.php.net/manual/en/book.pdo.php) 扩展和你所使用的数据库的 PDO 驱动(例如 MySQL 的 `pdo_mysql`)。对于使用关系型数据库来讲,这是基本要求。
驱动和扩展安装可用后,打开 `config/db.php` 修改里面的配置参数对应你的数据库配置。该文件默认包含这些内容:
```php
<?php
return [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=localhost;dbname=yii2basic',
'username' => 'root',
'password' => '',
'charset' => 'utf8',
];
```
`config/db/php` 是一个典型的基于文件的[配置](concept-configurations.md)工具。这个文件配置了数据库连接 [[yii\db\Connection]] 的创建和初始化参数,应用的 SQL 查询正是基于这个数据库。
上面配置的数据库连接可以在应用中通过 `Yii::$app->db` 访问。
> 补充:`config/db.php` 将被包含在应用配置文件 `config/web.php` 中,后者指定了整个[应用](structure-applications.md)如何初始化。请参考[配置](concept-configurations.md)小节了解更多信息。
创建活动记录 <a name="creating-active-record"></a>
-------------------------
创建一个继承自[活动记录](db-active-record.md)类的类 `Country`,把它放在 `models/Country.php`,去表示和获取 `country` 表的数据。
```php
<?php
namespace app\models;
use yii\db\ActiveRecord;
class Country extends ActiveRecord
{
}
```
这个 `Country` 类继承自 [[yii\db\ActiveRecord]]。你不用在里面写任何代码。只需要像现在这样,Yii 就能根据类名去猜测对应的数据表名。
> 补充:如果类名和数据表名不能直接对应,你可以重写 [[yii\db\ActiveRecord::tableName()|tableName()]] 方法去显式指定相关表名。
使用 `Country` 类你可以很容易地操作 `country` 表数据,就像这段代码:
```php
use app\models\Country;
// 获取 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();
```
> 补充:活动记录是面向对象、功能强大的访问和操作数据库数据的方式。你可以在[活动记录](db-active-record.md)小节了解更多信息。除此之外你还可以使用另一种更原生的称做[数据访问对象](db-dao)的方法操作数据库数据。
创建操作 <a name="creating-action"></a>
------------------
为了向最终用户显示国家数据,你需要创建一个操作。相比之前小节掌握的在 `site` 控制器中创建操作,在这里为所有和国家有关的数据新建一个控制器更加合理。新控制器名为 `CountryController`,并在其中创建一个 `index` 操作,如下:
```php
<?php
namespace app\controllers;
use yii\web\Controller;
use yii\data\Pagination;
use app\models\Country;
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` 表中取回所有数据。为了限定每个请求所返回的国家数量,查询在 [[yii\data\Pagination]] 对象的帮助下进行分页。 `Pagination` 对象的使命主要有两点:
* 为 SQL 查询语句设置 `offset``limit` 从句,确保每个请求只需返回一页数据(本例中每页是 5 行)。
* 在视图中显示一个由页码列表组成的分页器,这点将在后面的段落中解释。
在代码末尾,`index` 操作渲染一个名为 `index` 的视图,并传递国家数据和分页信息进去。
创建视图 <a name="creating-view"></a>
---------------
`views` 目录下先创建一个名为 `country` 的子目录。这个目录存储所有由 `country` 控制器渲染的视图。在 `views/country` 目录下创建一个名为 `index.php` 的视图文件,内容如下:
```php
<?php
use yii\helpers\Html;
use yii\widgets\LinkPager;
?>
<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 列表渲染出来。第二部分使用 [[yii\widgets\LinkPager]] 去渲染从操作中传来的分页信息。小部件 `LinkPager` 显示一个分页按钮的列表。点击任何一个按钮都会跳转到对应的分页。
尝试下 <a name="trying-it-out"></a>
-------------
浏览器访问下面的 URL 看看能否工作:
```
http://hostname/index.php?r=country/index
```
![国家列表](images/start-country-list.png)
首先你会看到显示着五个国家的列表页面。在国家下面,你还会看到一个包含四个按钮的分页器。如果你点击按钮 “2”,将会跳转到显示另外五个国家的页面,也就是第二页记录。如果观察仔细点你还会看到浏览器的 URL 变成了:
```
http://hostname/index.php?r=country/index&page=2
```
在这个场景里,[[yii\data\Pagination|Pagination]] 提供了为数据结果集分页的所有功能:
* 首先 [[yii\data\Pagination|Pagination]] 把 SELECT 的子查询 `LIMIT 5 OFFSET 0` 数据表示成第一页。因此开头的五条数据会被取出并显示。
* 然后小部件 [[yii\widgets\LinkPager|LinkPager]] 使用 [[yii\data\Pagination::createUrl()|Pagination::createUrl()]] 方法生成的 URL 去渲染翻页按钮。URL 中包含必要的参数 `page` 才能查询不同的页面编号。
* 如果你点击按钮 “2”,将会发起一个路由为 `country/index` 的新请求。[[yii\data\Pagination|Pagination]] 接收到 URL 中的 `page` 参数把当前的页码设为 2。新的数据库请求将会以 `LIMIT 5 OFFSET 5` 查询并显示。
总结 <a name="summary"></a>
-------
本小节中你学到了如何使用数据库。你还学到了如何取出并使用 [[yii\data\Pagination]] 和 [[yii\widgets\LinkPager]] 显示数据。
下一小节中你会学到如何使用 Yii 中强大的代码生成器 [Gii](tool-gii.md),去帮助你实现一些常用的功能需求,例如增查改善(CRUD)数据表中的数据。事实上你之前所写的代码全部都可以由 Gii 自动生成:)
\ No newline at end of file
使用表单
==================
本小节将介绍如何创建一个从用户那搜集数据的表单页面。该页将显示一个包含 name 输入框和 email 输入框的表单。当搜集完这两部分信息后,页面将会显示用户输入的信息。
为了实现这个目标,除了创建一个[操作](structure-controllers.md)和两个[视图](structure-views)外,你还需要创建一个[模型](structure-models.md)
贯穿整个小节,你将会学到:
* 创建一个[模型](structure-models.md)表示用户通过表单输入的数据
* 声明规则去验证输入的数据
*[视图](structure-views.md)中生成一个 HTML 表单
创建模型 <a name="creating-model"></a>
----------------
模型类 `EntryForm` 代表从用户那请求的数据,该类如下所示并存储在 `models/EntryForm.php` 文件中。请参考[类自动加载](concept-autoloading.md)小节获取更多关于类命名约定的介绍。
```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
<?php
$model = new EntryForm();
$model->name = 'Qiang';
$model->email = 'bad';
if ($model->validate()) {
// 验证成功!
} else {
// 失败!
// 使用 $model->getErrors() 获取错误详情
}
```
创建操作 <a name="creating-action"></a>
------------------
下面你得在 `site` 控制器中创建一个 `entry` 操作用于新建的模型。操作的创建和使用已经在[说一声你好](start-hello.md)小节中解释了。
```php
<?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` 代表[应用](structure-applications.md)实例,它是一个全局可访问的单例。同时它也是一个[服务定位器](concept-service-locator.md),能提供 `request`,`response`,`db` 等等特定功能的组件。在上面的代码里就是使用 `request` 组件来访问应用实例收到的 `$_POST` 数据。
用户提交表单后,操作将会渲染一个名为 `entry-confirm` 的视图去确认用户输入的数据。如果没填表单就提交,或数据包含错误(译者:如 email 格式不对),`entry` 视图将会渲染输出,连同表单一起输出的还有验证错误的信息。
> 注意:在这个简单例子里我们只是呈现了有效数据的确认页面。实践中你应该考虑使用 [[yii\web\Controller::refresh()|refresh()]] 或 [[yii\web\Controller::redirect()|redirect()]] 去避免[表单重复提交问题](http://en.wikipedia.org/wiki/Post/Redirect/Get)。
创建视图 <a name="creating-views"></a>
--------------
最后创建两个视图文件 `entry-confirm``entry`。他们会被刚才创建的 `entry` 操作渲染。
`entry-confirm` 视图简单地显示提交的 name 和 email 数据。视图文件保存在 `views/site/entry-confirm.php`
```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
<?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(); ?>
```
视图使用了一个功能强大的[小部件](structure-widgets.md) [[yii\widgets\ActiveForm|ActiveForm]] 去生成 HTML 表单。其中的 `begin()``end()` 分别用来渲染表单的开始和关闭标签。在这两个方法之间使用了 [[yii\widgets\ActiveForm::field()|field()]] 方法去创建输入框。第一个输入框用于 “name”,第二个输入框用于 “email”。之后使用 [[yii\helpers\Html::submitButton()]] 方法生成提交按钮。
尝试下 <a name="trying-it-out"></a>
-------------
用浏览器访问下面的 URL 看它能否工作:
```
http://hostname/index.php?r=site/entry
```
你会看到一个包含两个输入框的表单的页面。每个输入框的前面都有一个标签指明应该输入的数据类型。如果什么都不填就点击提交按钮,或填入格式不正确的 email 地址,你将会看到在对应的输入框下显示错误信息。
![验证错误的表单](images/start-form-validation.png)
输入有效的 name 和 email 信息并提交后,你将会看到一个显示你所提交数据的确认页面。
![输入数据的确认页](images/start-entry-confirmation.png)
### 效果说明 <a name="magic-explained"></a>
你可能会好奇 HTML 表单暗地里是如何工作的呢,看起来它可以为每个输入框显示文字标签,而当你没输入正确的信息时又不需要刷新页面就能给出错误提示,似乎有些神奇。
是的,其实数据首先由客户端 JavaScript 脚本验证,然后才会提交给服务器通过 PHP 验证。[[yii\widgets\ActiveForm]] 足够智能到把你在 `EntryForm` 模型中声明的验证规则转化成客户端 JavaScript 脚本去执行验证。如果用户浏览器禁用了 JavaScript, 服务器端仍然会像 `actionEntry()` 方法里这样验证一遍数据。这保证了任何情况下用户提交的数据都是有效的。
> 警告:客户端验证是提高用户体验的手段。无论它是否正常启用,服务端验证则都是必须的,请不要忽略它。
输入框的文字标签是 `field()` 方法生成的,内容就是模型中该数据的属性名。例如模型中的 `name` 属性生成的标签就是 `Name`
你可以在视图中自定义标签:
```php
<?= $form->field($model, 'name')->label('自定义 Name') ?>
<?= $form->field($model, 'email')->label('自定义 Email') ?>
```
> 补充:Yii 提供了相当多类似的小部件去帮你生成复杂且动态的视图。在后面你还会了解到自己写小部件是多么简单。你可能会把自己的很多视图代码转化成小部件以提高重用,加快开发效率。
总结 <a name="summary"></a>
-------
本小节指南中你接触了 MVC 设计模式的每个部分。你学到了如何创建一个模型代表用户数据并验证它的有效性。
你还学到了如何从用户那获取数据并在浏览器上回显给用户。这本来是开发应用的过程中比较耗时的任务,好在 Yii 提供了强大的小部件让它变得如此简单。
下一小节你将学习如何使用数据库,几乎每个应用都需要数据库。
使用 Gii 生成代码
========================
本小节将介绍如何使用 [Gii](tool-gii.md) 去自动生成 Web 站点常用功能的代码。使用 Gii 生成代码非常简单,只要按照 Gii 页面上的介绍输入正确的信息即可。
贯穿本小节,你将会学到:
* 在你的应用中开启 Gii
* 使用 Gii 去生成活动记录类
* 使用 Gii 去生成数据表操作的增查改删(CRUD)代码
* 自定义 Gii 生成的代码
开始 Gii <a name="starting-gii"></a>
------------
[Gii](tool-gii.md) 是 Yii 中的一个[模块](structure-modules.md)。你可以通过配置应用的 [[yii\base\Application::modules|modules]] 属性开启它。通常来讲在 `config/web.php` 文件中会有以下配置代码:
```php
$config = [ ... ];
if (YII_ENV_DEV) {
$config['bootstrap'][] = 'gii';
$config['modules']['gii'] = 'yii\gii\Module';
}
```
这段配置的意思是如果当前是[开发环境](concept-configurations.md#environment-constants),应用会包含 `gii` 模块,模块类是 [[yii\gii\Module]]。
如果你检查应用的[入口脚本](structure-entry-scripts.md) `web/index.php`,将看到这行代码将 `YII_ENV_DEV` 设为 true:
```php
defined('YII_ENV') or define('YII_ENV', 'dev');
```
代码设置应用处于开发模式下,按照上面的配置会打开 Gii 模块。你可以直接通过 URL 访问 Gii:
```
http://hostname/index.php?r=gii
```
![Gii](images/start-gii.png)
生成活动记录类 <a name="generating-ar"></a>
---------------------------------
选择 “Model Generator” (点击 Gii 首页的链接)去生成活动记录类。并像这样填写表单:
* Table Name: `country`
* Model Class: `Country`
![模型生成器](images/start-gii-model.png)
然后点击 “Preview” 按钮。你会看到 `models/Country.php` 被列在将要生成的文件列表中。可以点击文件名预览内容。
如果你已经创建过同样的文件,使用 Gii 可以覆写它,点击文件名旁边的 `diff` 能查看现有文件与将要生成的文件的内容区别。
![模型生成器预览](images/start-gii-model-preview.png)
想要覆写已存在文件,选中 “overwrite” 下的复选框然后点击 “Generator”。如果是新文件,只点击 “Generator” 就好。
接下来你会看到一个包含已生成文件的说明页面。如果生成过程中覆写过文件,还会有一条信息说明代码是重新生成覆盖的。
生成 CRUD 代码 <a name="generating-crud"></a>
--------------------
CRUD 代表增,查,改,删操作,这是绝大多数 Web 站点常用的数据处理方式。选择 Gii 中的 “CRUD Generator” (点击 Gii 首页的链接)去创建 CRUD 功能。之前的 “country” 例子需要像这样填写表单:
* Model Class: `app\models\Country`
* Search Model Class: `app\models\CountrySearch`
* Controller Class: `app\controllers\CountryController`
![CRUD 生成器](images/start-gii-crud.png)
然后点击 “Preview” 按钮。你会看到下述将要生成的文件列表。
[[NEED THE IMAGE HERE / 等待官方补充图片]]
如果你之前创建过 `controllers/CountryController.php``views/country/index.php` 文件(在指南的使用数据库小节),选中 “overwrite” 下的复选框覆写它们(之前的文件没能全部支持 CRUD)。
尝试下<a name="trying-it-out"></a>
-------------
用浏览器访问下面的 URL 查看生成代码的运行:
```
http://hostname/index.php?r=country/index
```
可以看到一个栅格显示着从数据表中获取的国家数据。支持在列头对数据进行排序,输入筛选条件进行筛选。
可以浏览详情,编辑,或删除栅格中的每个国家。还可以点击栅格上方的 “Create Country” 按钮通过表单创建新国家。
![国家的数据栅格](images/start-gii-country-grid.png)
![编辑一个国家](images/start-gii-country-update.png)
下面列出由 Gii 生成的文件,以便你也许想研习功能和实现,或修改它们。
* 控制器:`controllers/CountryController.php`
* 模型:`models/Country.php``models/CountrySearch.php`
* 视图:`views/country/*.php`
> 补充:Gii 被设计成高度可定制和可扩展的代码生成工具。使用它可以大幅提高应用开发速度。请参考 [Gii](tool-gii.md) 小节了解更多内容。
总结 <a name="summary"></a>
-------
本小节学习了如何使用 Gii 去生成为数据表中数据实现完整 CRUD 功能的代码。
说声 Hello
============
本小节描述如何在你的应用中创建一个新的 “Hello” 页面。为了做到这点,你将会创建一个[操作](structure-controllers.md#creating-actions)和一个[视图](structure-views.md)
* 应用将会分派页面请求给操作
* 操作将会依次渲染视图呈现 “Hello” 给最终用户
贯穿整个小节,你将会掌握三件事:
1. 如何创建一个[操作](structure-controllers.md)去响应请求,
2. 如何创建一个[视图](structure-views.md)去构造响应内容,
3. 以及一个应用如何分派请求给[操作](structure-controllers.md#creating-actions)
创建操作 <a name="creating-action"></a>
------------------
为了说 “Hello”,你需要创建一个 `say` [操作](structure-controllers.md#creating-actions),从请求中接收 `message` 参数并显示给最终用户。如果请求没有提供 `message` 参数,操作将显示默认参数 “Hello”。
> 补充:[操作](structure-controllers.md#creating-actions)是最终用户可以直接访问并执行的对象。操作被组织在[控制器](structure-controllers.md)中。一个操作的执行结果就是最终用户将会收到的响应内容。
操作必须声明在[控制器](structure-controllers.md)中。为了简单起见,你可以直接在 `SiteController` 控制器里声明 `say` 操作。这个控制器是由文件 `controllers/SiteController.php` 定义的。以下是一个操作的声明:
```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`[视图](structure-views.md)文件。 `message` 参数也被传入视图,这样就可以在里面使用。操作方法会返回渲染结果。结果会被应用接收并显示给最终用户的浏览器(作为整页 HTML 的一部分)。
创建视图 <a name="creating-view"></a>
---------------
[视图](structure-views.md)是你用来生成响应内容的脚本。为了说 “Hello”,你需要创建一个 `say` 视图,以便显示从操作方法中传来的 `message` 参数。
```php
<?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)攻击](http://en.wikipedia.org/wiki/Cross-site_scripting)
当然了,你大概会在 `say` 视图里放入更多内容。内容可以由 HTML 标签,纯文本,甚至 PHP 语句组成。实际上 `say` 视图就是一个由 [[yii\web\Controller::render()|render()]] 执行的 PHP 脚本。视图脚本输出的内容将会作为响应结果返回给应用。应用将依次输出结果给最终用户。
尝试下 <a name="trying-it-out"></a>
-------------
创建完操作和视图后,你就可以通过下面的 URL 访问新页面了:
```
http://hostname/index.php?r=site/say&message=Hello+World
```
![Hello World](images/start-hello-world.png)
这个 URL 将会输出包含 “Hello World” 的页面,页面和应用里的其它页面使用同样的头部和尾部。
如果你省略 URL 中的 `message` 参数,将会看到页面只显示 “Hello”。这是因为 `message` 被作为一个参数传给 `actionSay()` 方法,当省略它时,参数将使用默认的 `“Hello”` 代替。
> 补充:新页面和其它页面使用同样的头部和尾部是因为 [[yii\web\Controller::render()|render()]] 方法会自动把 `say` 视图执行的结果嵌入称为[布局](structure-views.md#layouts)的文件中,本例中是 `views/layouts/main.php`。
上面 URL 中的参数 `r` 需要更多解释。它代表[路由](runtime-routing.md),整个应用级的,指向特定操作的独立 ID。路由格式是 `控制器 ID/操作 ID`。应用接受请求的时候会检查参数,使用控制器 ID 去确定哪个控制器应该被用来处理请求。然后相应控制器将使用操作 ID 去确定哪个操作方法将被用来做具体工作。上述例子中,路由 `site/say` 将被解析至 `SiteController` 控制器和其中的 `say` 操作。因此 `SiteController::actionSay()` 方法将被调用处理请求。
> 补充:与操作一样,一个应用中控制器同样有唯一的 ID。控制器 ID 和操作 ID 使用同样的命名规则。控制器的类名源自于控制器 ID,移除了连字符,每个单词首字母大写,并加上 `Controller` 后缀。例子:控制器 ID `post-comment` 相当于控制器类名 `PostCommentController`。
总结 <a name="summary"></a>
-------
在本小节你已经接触了 MVC 设计模式中的控制器和视图部分。你创建了一个操作作为控制器的一部分去处理特定请求。然后你又创建了一个视图去构造响应内容。在这个小例子中,没有模型调用,唯一涉及到数据的地方是 `message` 参数。
你同样学习了 Yii 路由的相关内容,它是用户请求与控制器操作之间的桥梁。
在下一节,你将学习如何创建一个模型,以及添加一个包含 HTML 表单的页面。
更上一层楼
=============
通篇阅读完整个“入门”章节,你就完成了一个完整 Yii 应用的创建。在此过程中你学到了如何实现一些常用功能,例如通过 HTML 表单从用户那获取数据,从数据库中获取数据并以分页形式显示。你还学到了如何通过 [Gii](tool-gii.md) 去自动生成代码。使用 Gii 生成代码把 Web 开发中多数繁杂的过程转化为仅仅填写几个表单就行。
本小节将介绍一些有助于更好使用 Yii 的资源:
* 文档
- 权威指南:
顾名思义,指南详细描述了 Yii 的工作原理并提供了如何使用它的常规引导。这是最重要的 Yii 辅助资料,强烈建议在你开始写 Yii 代码之前阅读。
- 类参考手册:
描述了 Yii 中每个类的用法。在编码过程中这极为有用,能够帮你理清某个特定类,方法,和属性的用法。类参考手册最好在整个框架的语境下去理解。
- Wiki 文章:
Wiki 文章是 Yii 用户在其自身经验基础上分享出来的。大多数是使用教程或如何使用 Yii 解决特定问题。虽然这些文章质量可能并不如权威指南,但它们往往覆盖了更广泛的话题,并常常提供解决方案,所以它们也很有用。
- 书籍
* [扩展](http://www.yiiframework.com/extensions/):
Yii 拥有数以千计用户提供的扩展,这些扩展能非常方便的插入到应用中,使你的应用开发过程更加方便快捷。
* 社区
- [官方论坛](http://www.yiiframework.com/forum/)
- [GitHub](https://github.com/yiisoft/yii2)
- [Facebook](https://www.facebook.com/groups/yiitalk/)
- [Twitter](https://twitter.com/yiiframework)
- [LinkedIn](https://www.linkedin.com/groups/yii-framework-1483367)
运行应用程序 运行应用
==================== ====================
Yii 安装后,就有了一个可以运行的 Yii 应用程序,你可以通过 URL `http://hostname/basic/web/index.php` 安装 Yii 后,你就有了一个运行中的 Yii 应用,根据不同配置,可以通过 `http://hostname/basic/web/index.php``http://hostname/index.php` 访问。本小节将介绍应用的内建功能,如何组织代码,以及一般情况下应用如何处理请求。
`http://hostname/index.php` 访问它,具体要取决于你的配置。本章将介绍此应用程序的内置功能,代码的组织方式以及总体上程序是怎样处理请求
的。
> Info: 为简单起见,这个“入门”教程假设你已经将 `basic/web` 设置为了 Web 服务器的文档根目录。访问此程序的是类似
`http://hostname/index.php` 的 URL 。请根据你的实际情况,在下文描述中作相应调整。
> 补充:为简单起见,在整个“入门”章节中都假定你已经把 `basic/web` 设为 Web 服务器根目录并配置完毕,你访问应用的地址会是 `http://lostname/index.php` 或类似的。请按需调整 URL。
功能 <a name="functionality"></a> 功能 <a name="functionality"></a>
------------- -------------
安装的基础应用程序包含四个页面 一个安装完的基本应用包含四页
* 首页,访问 URL `http://hostname/index.php` 时显示, * 主页,当你访问 `http://hostname/index.php` 时显示,
* "About" 页面, * “About” 页面,
* "Contact" 页面,显示一个联络表单,允许终端用户通过电子邮件与你联系。 * “Contact” 页, 显示一个联系表单,允许终端用户通过 Email 联系你,
* 还有 "Login" 页面,显示一个登录表单,用于验证终端用户。请尝试使用“admin/admin”登录,你将发现主菜单上原来的“Login”变成了“Logout” * “Login” 页, 显示一个登录表单,用来验证终端用户。试着使用 “admin/admin” 登录,你可以看到当前是登录状态,已经可以“退出登录”了
这些页面共享一个通用的 header 和 footer。Header 含有一个主菜单,可以导航到不同的页面 这些页面使用同一个头部和尾部。头部包含了一个可以在不同页面间切换的导航栏
你应该还会在浏览器窗口的最下面发现有一个工具条。这是一个 Yii 提供的很有用的[调试工具](tool-debugger.md),它会记录并显示很多调试信息, 在浏览器底部可以看到一个工具栏。
例如日志消息,响应状态,数据库执行的查询等等。 这是个 Yii 提供的很有用的[调试工具](tool-debugger.md),可以记录并显示大量的调试信息,例如日志信息,响应状态,数据库查询等等。
应用程序结构 <a name="application-structure"></a> 应用结构 <a name="application-structure"></a>
--------------------- ---------------------
在你的应用程序中最重要的目录和文件是(假设程序的根目录是`basic`): 应用中最重要的目录和文件(假设应用根目录是 `basic`):
``` ```
basic/ 应用程序根目录 basic/ 应用根目录
composer.json 用于 Composer,描述包的信息 composer.json Composer 配置文件, 描述包信息
config/ 包含应用程序及其他配置信息。 config/ 包含应用配置及其它配置
console.php 控制台应用程序配置 console.php 控制台应用配置信息
web.php Web 应用程序配置 web.php Web 应用配置信息
commands/ 包含控制台命令类 commands/ 包含控制台命令类
controllers/ 包含控制器类 controllers/ 包含控制器类
models/ 包含模型类 models/ 包含模型类
runtime/ 包含 Yii 运行时产生的文件,例如日志和缓存文件等 runtime/ 包含 Yii 在运行时生成的文件,例如日志和缓存文件
vendor/ 包含已安装的 Componser 包,包括 Yii 框架本身。 vendor/ 包含已经安装的 Composer 包,包括 Yii 框架自身
views/ 包含视图文件 views/ 包含视图文件
web/ 应用程序 Web 根目录,包含可通过 Web 访问的文件 web/ Web 应用根目录,包含 Web 入口文件
assets/ 包含 Yii 发布的资源文件(javascript 和 css) assets/ 包含 Yii 发布的资源文件(javascript 和 css)
index.php 应用程序的入口(或引导)脚本 index.php 应用入口文件
yii Yii 控制台命令执行脚本 yii Yii 控制台命令执行脚本
``` ```
总体上,应用程序中的文件可以分为两类:位于 `basic/web` 中的和那些位于其他目录中的。前者可通过 HTTP (例如,在一个浏览器中)直接访问, 一般来说,应用中的文件可被分为两类:在 `basic/web` 下的和在其它目录下的。前者可以直接通过 HTTP 访问(例如浏览器),后者不能也不应该被直接访问。
后者则不能且不应该能。 Yii 实现了[模型-视图-控制器 (MVC)](http://wikipedia.org/wiki/Model-view-controller)设计模式,这点在上述目录结构中也得以体现。 `models` 目录包含了所有[模型类](structure-models.md)`views` 目录包含了所有[视图脚本](structure-views.md)`controllers` 目录包含了所有[控制器类](structure-controllers.md)
Yii 实现了 [模型-视图-控制器 (MVC)](http://wikipedia.org/wiki/Model-view-controller) 设计模式,在上述目录组织中也有体现。
`models` 目录中包含了所有的[模型类](structure-models.md)`views` 目录包含了所有的[视图脚本](structure-views.md)
`controllers` 目录包含了所有[控制器类](structure-controllers.md)
下图展示了一个应用程序的静态结构。 以下图表展示了一个应用的静态结构:
![应用程序的静态结构](images/application-structure.png) ![应用静态结构](images/application-structure.png)
每个应用程序都有一个入口脚本 `web/index.php`,它是应用程序中仅有的可通过 Web 访问的 PHP 脚本。这个入口脚本接收一个传入请求并创建一个 每个应用都有一个入口脚本 `web/index.php`,这应该是整个应用中唯一可以访问的 PHP 脚本。入口脚本接受一个 Web 请求并创建[应用](structure-application.md)实例去处理它。 [应用](structure-applications.md)在它的[组建](concept-components.md)的辅助下解析请求,并分派请求至 MVC 元素。[视图](structure-views.md)使用[小部件](structure-widgets.md)去创建复杂和动态的用户界面。
[应用程序](structure-applications.md) 实例处理该请求。[应用程序](structure-applications.md)在其[组件](concept-components.md)
的帮助下解析请求并将请求分派到 MVC 元素上。[视图](structure-views.md)中使用[挂件](structure-widgets.md)协助构建复杂动态的用户接口
元素。
请求生命周期 <a name="request-lifecycle"></a> 请求生命周期 <a name="request-lifecycle"></a>
----------------- -----------------
下图展示了一个应用是如何处理请求的。 以下图表展示了一个应用如何处理请求:
![请求的生命周期](images/application-lifecycle.png) ![请求生命周期](images/application-lifecycle.png)
1. 一个用户提交了对[入口脚本(entry script)](structure-entry-scripts.md) `web/index.php` 的请求。 1. 用户向 [入口脚本](structure-entry-scripts.md) `web/index.php` 发起请求。
2. 入口脚本加载应用程序[配置信息(configuration)](concept-configurations.md)并创建一个[应用程序](structure-applications.md) 2. 入口脚本加载应用 [配置](concept-configurations.md)并创建一个[应用](structure-applications.md)实例去处理请求。
例处理该请求。 3. 应用通过[请求](runtime-request.md)组件解析请求的[路由](runtime-routing.md)
3. 应用程序在[请求(request)]应用程序组件的协助下解析所请求的[路由(route)](runtime-routing.md) 4. 应用创建一个[控制器](structure-controllers.md)实例去处理请求。
4. 应用程序创建一个[控制器(controller)](structure-controllers.md)实例处理该请求。 5. 控制器创建一个[操作](structure-controllers.md)实例并针对操作执行过滤器。
5. 控制器创建了一个[动作(action)](structure-controllers.md)实例,执行动作中的过滤器(filter)。 6. 如果任何一个过滤器返回失败,则操作退出。
6. 如果有任何一个过滤器处理失败,则动作取消。 7. 如果所有过滤器都通过,操作将被执行。
7. 如果所有的过滤器都执行通过,则动作执行。 8. 操作会加载一个数据模型,或许是来自数据库。
8. 动作载入一个数据模型,可能是从一个数据库中加载。 9. 操作会渲染一个视图,把数据模型提供给它。
9. 动作渲染一个视图(view),给它提供数据模型。 10. 渲染结果返回给[响应](runtime-responses.md)组件。
10. 渲染结果返回给[响应(response)](runtime-responses.md)应用程序组件。 11. 响应组件发送渲染结果给用户浏览器。
11. 响应组件发送渲染结果到用户的浏览器。
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment