[关闭]
@a5635268 2016-02-06T21:10:08.000000Z 字数 26678 阅读 1705

Yii框架之MVC

源码分析与使用笔记


MVC

控制器

  1. <?php
  2. namespace app\controllers;
  3. use yii\web\Controller;
  4. use app\models\EntryForm;
  5. class IndexController extends Controller
  6. {
  7. private $query;
  8. public function init(){
  9. parent::init();
  10. $this -> query = new \yii\db\Query();
  11. }
  12. //r=Index/hello
  13. public function actionHello($name = 'world'){
  14. return $this->render('hello', ['name' => $name]);
  15. }
  16. // yiibasc.com/index.php?r=index/mvc
  17. public function actionMvc(){
  18. /*
  19. * * 关于C层的概念 * *
  20. * 1. 参数可以为数组类型:参数 $id 会使用数组值 ['123'] , 如果请求为 http://hostname/index.php?r=post/view&id=123 , 参数 $id 会获取相同数 组值,因为无类型的 '123' 会自动转成数组。
  21. * 2. 控制器ID的实现方式是大驼峰,但真正访问时变成小写+连字符
  22. * 3. 可以通过yii\base\Application::controllerMap或者配置文件中建立类的映射关系。
  23. * 4. 在控制器创建和配置后,yii\base\Controller::init() 方法会被调用。
  24. * 5. 默认情况下每个 beforeAction() 方法会触发一个 beforeAction 事件,在事件中你可以追加事 件处理操作;同理还有一个afterAction。
  25. * 6. 在设计良好的应用中,控制器很精练,包含的操作代码简短; 如果你的控制器很复杂,通常意味着需要重 构,转移一些代码到其他类中。
  26. */
  27. /* 关于c层的疑问:
  28. * 1. 独立操作actions是什么概念,有什么用?
  29. */
  30. $model = new EntryForm;
  31. $request = \Yii::$app->request;
  32. if($request -> isPost){
  33. $post = $request -> post();
  34. $model -> doTest($post);
  35. }
  36. //Html::encode($name)
  37. return $this->render('entry', ['model' => $model]);
  38. }
  39. }

模型

dataAccessObjects

http://www.yiichina.com/doc/guide/2.0/db-dao

  1. public function dataAccessObjects(){
  2. $connection = \Yii::$app->db;
  3. # 执行select语句
  4. //1. 返回所有
  5. $command = $connection->createCommand('SELECT * FROM sc_hospital');
  6. $data = $command->queryAll();
  7. //2. 返回单行
  8. $command = $connection->createCommand('SELECT * FROM sc_hospital order by rand()');
  9. $data = $command->queryOne();
  10. //3. 返回多行单值
  11. $command = $connection -> createCommand('SELECT name FROM sc_hospital');
  12. $data = $command -> queryColumn();
  13. //4. 返回标量值/计算值
  14. $command = $connection -> createCommand('SELECT count(*) FROM sc_hospital');
  15. $data = $command -> queryScalar();
  16. # 执行DML语句
  17. $command = $connection->createCommand('update sc_hospital set name = "刚哥医院" WHERE hosp_id = 7');
  18. $res = $command->execute(); //成功返回true;
  19. //insert
  20. $res = $connection->createCommand()->insert('sc_hospital', [
  21. 'name' => '和平医院',
  22. 'region_id' => 2,
  23. ])->execute();
  24. //一次插入多行
  25. $res = $connection->createCommand()->batchInsert('sc_hospital', ['name', 'region_id'], [
  26. ['长寿医院', 30],
  27. ['北京医院', 20],
  28. ['上海第一医院', 25],
  29. ])->execute(); //返回插入的条数
  30. // UPDATE
  31. $connection->createCommand()->update('user', ['status' => 1], 'age > 30')->execute();
  32. // DELETE
  33. $connection->createCommand()->delete('user', 'status = 0')->execute();
  34. //{{%hospital}} 自动增加前缀
  35. $sql = "SELECT COUNT('hosp_id') FROM {{%hospital}}";
  36. $rowCount = $connection->createCommand($sql)->queryScalar();
  37. //安全的返回表名或列名
  38. $column = $connection->quoteColumnName('hosp_id');
  39. $table = $connection->quoteTableName('{{%hospital}}');
  40. $sql = "SELECT COUNT($column) FROM $table";
  41. $rowCount = $connection->createCommand($sql)->queryScalar();
  42. //预处理语句(防注入)
  43. $command = $connection->createCommand('SELECT * FROM post WHERE id=:id');
  44. $command->bindValue(':id', $_GET['id']);
  45. $post = $command->query();
  46. //在执行前绑定变量,然后在每个执行中改变变量的值(一般用在循环中)比较高效.
  47. $command = $connection->createCommand("DELETE FROM $table WHERE hosp_id=:id");
  48. $command->bindParam(':id', $id);
  49. //删除$table表中大于18而小于26的; 尼玛这个id后赋值的原理是引用传值,先把id这个地址传进入,在execute()方法调用之前改变都有效;
  50. $id = 18;
  51. while($id < 26){
  52. $id ++;
  53. $command->execute();
  54. }
  55. //执行事务,在事务内部有抛异常
  56. $transaction = $connection->beginTransaction();
  57. try {
  58. $connection->createCommand($sql1)->execute();
  59. $connection->createCommand($sql2)->execute();
  60. // ... 执行其他 SQL 语句 ...
  61. $transaction->commit();
  62. } catch(Exception $e) {
  63. $transaction->rollBack();
  64. }
  65. // 多个事务参考
  66. $transaction1 = $connection->beginTransaction();
  67. try {
  68. $connection->createCommand($sql1)->execute();
  69. // 内部事务
  70. $transaction2 = $connection->beginTransaction();
  71. try {
  72. $connection->createCommand($sql2)->execute();
  73. $transaction2->commit();
  74. } catch (Exception $e) {
  75. $transaction2->rollBack();
  76. }
  77. $transaction1->commit();
  78. } catch (Exception $e) {
  79. $transaction1->rollBack();
  80. }
  81. # http://www.yiichina.com/doc/guide/2.0/db-dao 【数据库复制和读写分离】 等主从复制和分离的博文完结后再来看
  82. # 操作数据库模式 更多查看:yii\db\Schema
  83. $schema = $connection->getSchema();
  84. //获得所有表名
  85. $tables = $schema->getTableNames();
  86. //可以获得该表的所有DDL信息
  87. $res = $schema -> getTableSchema('table_name');
  88. //可以获得数据库中所有表的DDL信息
  89. $res = $schema -> getTableSchemas('database');
  90. }

queryBuilder

http://www.yiichina.com/doc/guide/2.0/db-query-builder

  1. public function queryBuilder(){
  2. $connection = \Yii::$app->db;
  3. $table = $connection->quoteTableName('{{%hospital}}');
  4. $query = new \yii\db\Query();
  5. # 查询构建器
  6. $rows = (new \yii\db\Query())->select(['hosp_id', 'name'])->from($table)->where(['level' => '1'])->limit(3)->all();
  7. /*
  8. * 当你调用 yii\db\Query 当中的一个查询方法的时候,实际上内在的运作机制如下:
  9. * 1. 在当前 yii\db\Query 的构造基础之上,调用 yii\db\QueryBuilder 来生成一条 SQL 语句;
  10. * 2. 利用生成的 SQL 语句创建一个 yii\db\Command 对象;
  11. * 3. 调用 yii\db\Command 的查询方法(例如,queryAll())来执行这条 SQL 语句,并检索数据。
  12. */
  13. /*
  14. * # select
  15. * 1. select(['user_id' => 'user.id', 'email']); //user.id as user_id,如果不用此方法默认是*
  16. * 2. select可以用字符串,但当使用函数时就必须要用到数组了$query->select(["CONCAT(first_name, ' ', last_name) AS full_name", 'email']);
  17. * 3. $query->select('user_id')->distinct(); 去除重复行
  18. * 4. $query->select(['id', 'username']) ->addSelect(['email']); 增加附加字段,一般用在判断中
  19. */
  20. /*
  21. * # from
  22. * 1. $query->from('public.user u, public.post p');
  23. * 2. $query->from(['u' => 'public.user', 'p' => 'public.post']);
  24. * 3. $subQuery = (new Query())->select('id')->from('user')->where('status=1');
  25. * $query->from(['u' => $subQuery]);//SELECT * FROM (SELECT `id` FROM `user` WHERE status=1) u
  26. */
  27. /*
  28. * # where
  29. * 1. 字符串格式,例如: 'status=1';
  30. * 2. 哈希格式,例如: ['status' => 1, 'type' => 2]
  31. * 3. 操作符格式,例如: ['like', 'name', 'test']
  32. */
  33. //字符串格式
  34. $query->where('status=:status', [':status' => $status]); //防注入,动态替换掉:status
  35. $query->where('status=:status')->addParams([':status' => $status]);
  36. //哈希格式
  37. // ...WHERE (`status` = 10) AND (`type` IS NULL) AND (`id` IN (4, 8, 15))
  38. $query->where(
  39. [
  40. 'status' => 10,
  41. 'type' => null,
  42. 'id' => [4, 8, 15],
  43. ]
  44. );
  45. $userQuery = (new Query())->select('id')->from('user');
  46. // ...WHERE `id` IN (SELECT `id` FROM `user`)
  47. $query->where(['id' => $userQuery]);
  48. /*
  49. * # where
  50. * [操作符, 操作数1, 操作数2, ...]
  51. * 1. and : 操作数会被 AND 关键字串联起来。例如, ['and', 'id=1', 'id=2'] 将会生成 id=1 AND id=2 。如果操作数是一个数组,它也会按上述规则转换成 字符串。例如,
  52. * ['and', 'type=1', ['or', 'id=1', 'id=2']] 将会生成 type=1 AND (id=1 OR id=2) 。 这个方法不会自动加引号或者转义。
  53. *
  54. * 2. or : 用法和 and 操作符类似,这里就不再赘述。
  55. *
  56. * 3. between : 第一个操作数为字段名称,第二个和第三个操作数代表的是这个字段的取值范围。例如, ['between', 'id', 1, 10] 将会生成 id BETWEEN 1 AND 10 。
  57. * not between : 用法和 BETWEEN 操作符类似,这里就不再赘述。
  58. *
  59. * 4. in : 第一个操作数应为字段名称或者 DB 表达式。第二个操作符既可以是一个数组, 也可以是一个 Query 对象。它会转换成 IN 条件语句。如果第二个操作数是一个 数组,那么它代表的是字段或 DB 表达式的取值范围。如果第二个操作数是 Query 对象,那么这个子查询的结果集将会作为第一个操 作符的字段或者 DB 表达式的取值范围。 例如, ['in', 'id', [1, 2, 3]] 将生成 id IN (1, 2, 3) 。 该方法将正确地为字段名加引号以及为取值范围转义。 in 操作符还支持组合字段,此时, 操作数1应该是 一个字段名数组,而操作数2应该是一个数组或者 Query 对象, 代表这些字段的取值范围。
  60. * not in : 用法和 in 操作符类似,这里就不再赘述。
  61. *
  62. * 5. like : 第一个操作数应为一个字段名称或 DB 表达式,第二个操作数可以使字符串或数组, 代表第一 个操作数需要模糊查询的值。比如, ['like', 'name', 'tester'] 会生成 name LIKE '%tester%' 。 如果范围值是一个数组,那么将会生成用 AND 串联起来的 多个 like 语句。例如, ['like', 'name', ['test', 'sample']] 将会生成 name LIKE '%test%' AND name LIKE '%sample%' 。 你也可以提供第三个可选的操作数来指定应该如何转义数值当中的特殊字符。 该操作数是一个从需要被转义的特殊字符到转义副本的数组映射。 如果没有提供这个操作数,将会使用默认的转义映射。 如果需要禁用转义的功能, 只需要将参数设置为 false 或者传入一个空数组即可。需要注意的是, 当使用转义映射(又或者没有提供第三个操作数的时候),第二个操作数的值的前后 将会被加上百分 号。
  63. *
  64. * 6. exists : 需要一个操作数,该操作数必须是代表子查询 yii\db\Query 的一个实例, 它将会构建一个 EXISTS (sub-query) 表达式。
  65. *
  66. * 7. > , <= , 或者其他包含两个操作数的合法 DB 操作符: 第一个操作数必须为字段的名称, 而第二个操 作数则应为一个值。例如, ['>', 'age', 10] 将会生成 age>10 。
  67. *
  68. * 8. 你可以使用 yii\db\Query::andWhere() 或者 yii\db\Query::orWhere() 在原有条件的基础上 附加额外的 条件。你可以多次调用这些方法来分别追加不同的条件
  69. *
  70. * 9. 使用yii\db\Query::filterWhere()可以忽略空值,类似于 [yii\db\Query::andWhere()|andWhere()]] 和 yii\db\Query::orWhere(), 你可以使用 yii\db\Query::andFilterWhere() 和 yii\db\Query::orFilterWhere() 方法 来追加额外的过滤条件。
  71. */
  72. # yii\db\Query::orderBy()
  73. // ... ORDER BY `id` ASC, `name` DESC
  74. $query->orderBy([
  75. 'id' => SORT_ASC,
  76. 'name' => SORT_DESC,
  77. ]);
  78. $query->orderBy('id ASC, name DESC');
  79. $query->orderBy('id ASC')->addOrderBy('name DESC');
  80. # yii\db\Query::groupBy()
  81. // ... GROUP BY `id`, `status`
  82. $query->groupBy(['id', 'status']);
  83. $query->groupBy('id, status');
  84. $query->groupBy(['id', 'status'])->addGroupBy('age');
  85. # yii\db\Query::having()
  86. // ... HAVING `status` = 1,使用方式和where一样
  87. $query->having(['status' => 1]);
  88. // ... HAVING (`status` = 1) AND (`age` > 30)
  89. $query->having(['status' => 1])->andHaving(['>', 'age', 30]);
  90. # yii\db\Query::limit() 和 yii\db\Query::offset()
  91. $query->limit(10)->offset(20);
  92. # yii\db\Query::join()
  93. // ... LEFT JOIN post ON post.user_id = user.id
  94. $query->join('LEFT JOIN', 'post', 'post.user_id = user.id');
  95. /*
  96. * join($type, $table, $on, $params);
  97. * $on: 可选参数,连接条件,即 ON 子句。请查阅 where() 获取更多有关于条件定义的细节。
  98. * $params: 可选参数,与连接条件绑定的参数(防注入替换)。
  99. */
  100. $query->leftJoin('post', 'post.user_id = user.id');
  101. $subQuery = (new \yii\db\Query())->from('post');
  102. $query->leftJoin(['u' => $subQuery], 'u.id = author_id'); //将子查询放到一个数组当中,而数组当中的键,则为这个子查询的别名
  103. # yii\db\Query::union()
  104. $query1 = (new \yii\db\Query())->select("id, category_id AS type, name")->from('post')->limit(10);
  105. $query2 = (new \yii\db\Query())->select('id, type, name')->from('user')->limit(10);
  106. $query1->union($query2);
  107. # 查询输出
  108. // SELECT `id`, `email` FROM `user`
  109. $rows = (new \yii\db\Query())->select(['id', 'email'])->from('user')->all();
  110. // 返回结果集的第一行:SELECT * FROM `user` WHERE `username` LIKE `%test%`
  111. // 注意one方法只返回查询结果当中的第一条数据, 条件语句中不会加上 LIMIT 1 条件。如果你清楚的知道查询将会只返回一行或几行数据 (例如, 如果你是通过某些主键来查询的),这很好也提倡这样做。但是,如果查询结果 有机会返回大量的数据时,那么你应该显示调用 limit(1) 方法,以改善性能。
  112. $row = (new \yii\db\Query())->from('user')->where(['like', 'username', 'test'])->one();
  113. // 执行 SQL: SELECT COUNT(*) FROM `user` WHERE `last_name`=:last_name
  114. $count = (new \yii\db\Query())->from('user')->where(['last_name' => 'Smith'])->count();
  115. //yii\db\Query::column(): 返回结果集的第一列。
  116. $res = $this -> query -> from('sc_user') -> column();
  117. //yii\db\Query::scalar(): 返回结果集的第一行第一列的标量值。
  118. $res = $this -> query -> from('sc_user') -> scalar();
  119. //yii\db\Query::exists(): 无值就返回false;
  120. $res = (new \yii\db\Query()) -> from('sc_user') -> where(['username' => 'admin2']) -> exists();
  121. //想要测试或者使用一个由 yii\db\Query 对象创建的 SQL 语句。 你可以使用以下的代码来达到目的:
  122. $command = (new \yii\db\Query())
  123. ->select(['id', 'email'])
  124. ->from('user')
  125. ->where(['last_name' => 'Smith'])
  126. ->limit(10)
  127. ->createCommand();
  128. // 打印 SQL 语句
  129. echo $command->sql;
  130. // 打印被绑定的参数
  131. print_r($command->params);
  132. // 返回查询结果的所有行
  133. $rows = $command->queryAll();
  134. # 查询结果重建数组索引
  135. // 返回 [100 => ['id' => 100, 'username' => '...', ...], 101 => [...], 103 => [...], ...]
  136. $query = (new \yii\db\Query())->from('user')->limit(10)->indexBy('id')->all();
  137. //使用表达式
  138. $query = (new \yii\db\Query())->from('user')->indexBy(function ($row) { return $row['id'] . $row['username']; }
  139. )->all();
  140. # 批处理查询(可用在采集,数据处理时)
  141. /*
  142. * yii\db\Query::batch() 和 yii\db\Query::each() 方法将会返回一个实现了Iterator 接口 yii\db\BatchQueryResult 的对象,可以用在 foreach 结构当中使用。在第一次迭代取数据的时候, 数据库会执行一次 SQL 查询,然后在剩下的迭代中,将直接从结果集中批量获取数据。默认情况下, 一批的大小为 100,也就意味着一批获取的数据是 100 行。你可以通过给 batch() 或者 each() 方法的第一个参数传值来改变每批行数的大小。
  143. 相对于 yii\db\Query::all() 方法,批处理查询每次只读取 100 行的数据到内存。 如果你在处理完这些数据后及时丢弃这些数据,那么批处理查询可以很好的帮助降低内存的占用率。
  144. 如果你通过 yii\db\Query::indexBy() 方法为查询结果指定了索引字段,那么批处理查询将仍然保持相对应的索引方案
  145. */
  146. $query = (new \yii\db\Query())
  147. ->from('user')
  148. ->indexBy('username');
  149. foreach ($query->batch() as $users) {
  150. // $users 的 “username” 字段将会成为索引
  151. }
  152. foreach ($query->each() as $username => $user) {
  153. }
  154. }

非数据库模型

http://www.yiichina.com/doc/guide/2.0/structure-models

  1. <?php
  2. namespace app\models;
  3. use yii\base\Model;
  4. class EntryForm extends Model
  5. {
  6. /*
  7. * 1. yii\base\Model支持 ArrayAccess 数组访问 和 ArrayIterator 数组迭代器;可以拿模型当一个大数组来用
  8. * 2. 这是一个普通的模型,与数据库没有关联
  9. * 3. 默认情况下你的模型类直接从yii\base\Model继承,所有non-static public非静态公有成员变量都是属性,另一种方式是可覆盖 yii\base\Model::attributes() 来定义属性,该方法返回模型的属性名(直接返回一个数组既可)。
  10. * 4. 可以通过$this -> getAttributeLabel('name')来获取name的表单name,可以覆盖yii\base\Model::attributeLabels() 方法明确指定属性标签(见以下方法)
  11. * 5. 可以通过覆盖 yii\base\Model::scenarios()方法来自定义行为,用于不同场景下的表单验证和收集
  12. * 6. 可以通过$model->attributes = \Yii::$app->request->post('ContactForm');来为模型的属性进行赋值;该种赋值方式是基于场景指定的;非场景指定的属性不被赋值
  13. *
  14. */
  15. public $name;
  16. public $email;
  17. public $password;
  18. public function rules($arr = array())
  19. {
  20. return $arr?:[
  21. [['name', 'email'], 'required'], //required 换成 safe就表示不需要验证,如果在属性名前加一个!号就该属性就不被块赋值
  22. ['email', 'email'],
  23. ];
  24. }
  25. public function scenarios(){
  26. /*
  27. * 场景作为属性来设置
  28. $model = new User;
  29. $model->scenario = 'login';
  30. * 场景通过构造初始化配置来设置
  31. $model = new User(['scenario' => 'login']);
  32. */
  33. return [
  34. 'login' => ['username','password'], //用于登录时
  35. 'regiser' => ['username','email','password'] //用于注册时,在验证规则中加上 'on' => 'register'既可,如果没有指定on就是在所有的场景中使用
  36. ];
  37. }
  38. //默认实现会返回所有yii\base\Model::rules()方法申明的验证规则中的场景, 当覆盖 scenarios() 时,如果你想在默认场景外使用新场景,可以编写类似如下代码
  39. public function scenarios()
  40. {
  41. $scenarios = parent::scenarios();
  42. $scenarios['login'] = ['username', 'password'];
  43. $scenarios['register'] = ['username', 'email', 'password'];
  44. return $scenarios;
  45. }
  46. public function attributeLabels(){
  47. //还可以应对多语言情况哦
  48. return [
  49. 'name' => '姓名',
  50. 'email' => '邮件'
  51. ];
  52. }
  53. public function doTest($data){
  54. if(!($this -> load($data) && $this -> validate())){
  55. //输出错误
  56. print_r($this -> getErrors());
  57. };
  58. }
  59. // 明确列出每个字段,特别用于你想确保数据表或模型属性改变不会导致你的字段改变(保证后端的API兼容).
  60. public function fields()
  61. {
  62. return [
  63. // 字段名和属性名相同
  64. 'id',
  65. // 字段名为 "email",对应属性名为 "email_address"
  66. 'email' => 'email_address',
  67. // 字段名为 "name", 值通过PHP代码返回
  68. 'name' => function () {
  69. return $this->first_name . ' ' . $this->last_name;
  70. },
  71. ];
  72. }
  73. // 过滤掉一些字段,特别用于你想继承父类实现并不想用一些敏感字段
  74. public function fields()
  75. {
  76. $fields = parent::fields();
  77. // 去掉一些包含敏感信息的字段
  78. unset($fields['auth_key'], $fields['password_hash'], $fields['password_reset_token']);
  79. return $fields;
  80. }
  81. }

AR模型

http://www.yiichina.com/doc/guide/2.0/db-active-record

  1. <?php
  2. namespace app\models;
  3. use yii\db\ActiveRecord;
  4. class Orders extends ActiveRecord
  5. {
  6. const WAIT = 1; //相较于直接在代码中写死字符串或数字,使用一个更有意义的常量名称是一种更好的编程习惯。
  7. const CHECK = 2;
  8. /**
  9. * @return string 返回该AR类关联的数据表名
  10. */
  11. public static function tableName()
  12. {
  13. return '{{%orders}}';
  14. }
  15. public function query(){
  16. //查询方法大致跟queryBuilder一样,但是,返回的是对象;
  17. $res = $this::find()->where(['and','user_id=27',['in','o_id',[328,329,330]]]) ->orderBy('o_id')->all();
  18. $res = $this::find() -> where(['o_id' => 328]) -> one();
  19. $res = $this::find() -> indexBy('o_id') -> all();
  20. $res = $this::find() -> count(); //直接是数值不是对象
  21. // 用原生 SQL 语句检索客户:
  22. $sql = 'SELECT * FROM customer';
  23. $customers = Customer::findBySql($sql)->all();
  24. //返回主键为328的数据
  25. $res = $this::findOne(328);
  26. //可以把条件
  27. $res = $this::findOne(
  28. [
  29. 'o_id' => 328,
  30. 'status' => $this::WAIT
  31. ]
  32. );
  33. // 返回id为1、2、3的一组客户
  34. $customers = Customer::findAll([1, 2, 3]);
  35. // 返回所有状态为 "deleted" 的客户
  36. $customer = Customer::findAll(
  37. [
  38. 'status' => Customer::STATUS_DELETED,
  39. ]
  40. );
  41. // 以数组而不是对象形式取回客户信息:
  42. $customers = Customer::find()->asArray()->all();
  43. }
  44. # 关联查询篇
  45. /*
  46. * 由于在Component.php的_get()函数中实现了$getter = 'get'.modelName,然后自动补上了all方法返回,所以以下方法可以在控制器中通过 Orders -> users 获得数据
  47. * yii\db\ActiveQuery对象有关联上下文的相关信息,因此可以只查询关联数据。也就是说在 Orders -> users 前面做的任何关于order的一条查询,都可以关联出来
  48. */
  49. public function getUsers(){
  50. // 客户和订单通过 Order.customer_id -> id 关联建立一对多关系
  51. return $this->hasMany(Users::className(), ['user_id' => 'user_id']);
  52. }
  53. /*
  54. * //取得该订单所关联的客户
  55. * $orders = Orders::findOne(1);
  56. * $users = $orders -> Customer;
  57. * 相当于
  58. * SELECT * FROM customer WHERE id=1;
  59. * SELECT * FROM order WHERE customer_id=1;
  60. * 再次用表达式 $customer->orders将不会执行第二次 SQL 查询, SQL 查询只在该表达式第一次使用时执行。 数据库访问只返回缓存在内部前一次取回的结果集,如果你想查询新的 关联数据,先要注销现有结果集:unset($customer->orders);。
  61. *
  62. * 一对多关联 ↓↓
  63. * Order.customer_id 要被关联的放在前面
  64. * return $this->hasMany(Order::className(), ['customer_id' => 'id']);
  65. */
  66. public function getCustomer()
  67. {
  68. //想取什么就关联什么,而且是在同一命名空间下,也不用use了
  69. return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
  70. }
  71. /**
  72. * 多对多关联1
  73. * # question: 没搞明白,关联不起来,主要是外键都在中间表内
  74. * @var $this Hospital
  75. * @return
  76. */
  77. public function getOutpatient(){
  78. return $this->hasMany(Outpatient::className(), ['opt_id' => 'hosp_id'])
  79. ->viaTable('sc_hospital_outpatient', ['hosp_id' => 'opt_id']);
  80. }
  81. /**
  82. * 换成getOutpatient和getHospOpt配合起来就可以关联
  83. * @var $this Hospital
  84. * $hospObj = Hospital::findOne(18);
  85. * $opts = $hospObj -> outpatient;
  86. * @return
  87. */
  88. public function getOutpatient(){
  89. return $this -> hasMany(Outpatient::className(),['opt_id' => 'opt_id']) -> select(['opt_name','opt_id']) -> indexBy('opt_id') -> via('hospOpt') -> asArray();
  90. }
  91. public function getHospOpt(){
  92. return $this -> hasMany(HospOpt::className(),['hosp_id' => 'hosp_id']);
  93. }
  94. //批量处理数据
  95. public function batch(){
  96. // 一次提取 10 个客户信息
  97. foreach (Customer::find()->batch(10) as $customers) {
  98. // $customers 是 10 个或更少的客户对象的数组
  99. }
  100. // 一次提取 10 个客户并一个一个地遍历处理
  101. foreach (Customer::find()->each(10) as $customer) {
  102. // $customer 是一个 ”Customer“ 对象
  103. }
  104. // 贪婪加载模式的批处理查询
  105. foreach (Customer::find()->with('orders')->each() as $customer){
  106. }
  107. }
  108. //待筛选条件;
  109. public function getBigOrders($threshold = 100)
  110. {
  111. return $this->hasMany(Order::className(), ['customer_id' => 'id'])
  112. ->where('subtotal > :threshold', [':threshold' => $threshold])
  113. ->orderBy('id');
  114. }
  115. }
延迟加载和即时加载
  1. # 延时加载
  2. // SQL executed: SELECT * FROM customer WHERE id=1
  3. $customer = Customer::findOne(1);
  4. // SQL executed: SELECT * FROM order WHERE customer_id=1
  5. $orders = $customer->orders;
  6. // 没有 SQL 语句被执行
  7. $orders2 = $customer->orders; //取回上次查询的缓存数据
  8. # 但是会在以下情况出现性能问题,会查一百多次数据库
  9. // SQL executed: SELECT * FROM customer LIMIT 100
  10. $customers = Customer::find()->limit(100)->all();
  11. foreach ($customers as $customer) {
  12. // SQL executed: SELECT * FROM order WHERE customer_id=...
  13. $orders = $customer->orders;
  14. // ...处理 $orders...
  15. }
  16. # 解决方案:
  17. // SQL executed: SELECT * FROM customer LIMIT 100;
  18. // SELECT * FROM orders WHERE customer_id IN (1,2,...)
  19. // 此处的orders要在Customer模型内要声明其关系
  20. $customers = Customer::find()->limit(100)
  21. ->with('orders')->all();
  22. foreach ($customers as $customer) {
  23. // 没有 SQL 语句被执行
  24. $orders = $customer->orders;
  25. // ...处理 $orders...
  26. }
  27. # Example
  28. //取前面100个医院,然后通过医院模型中声明的getOutpatient关系取出该医院下面的科室
  29. # question:如何筛选数据列
  30. $hospObj = Hospital::find() -> limit(100) -> with('outpatient') -> all();
  31. foreach($hospObj as $hosp){
  32. // 以下操作都不走数据库,都是走SPL迭代器
  33. echo $hosp -> name; //取出当前医院的名称
  34. //取出当前医院下的所有科室
  35. $opts = $hosp -> outpatient;
  36. echo "<pre>";
  37. print_r($opts);
  38. echo "<pre/> <hr />";
  39. }
  1. $customer = Customer::findOne(1);
  2. // 延迟加载: SELECT * FROM order WHERE customer_id=1 AND subtotal>100
  3. $orders = $customer->getOrders()->where('subtotal>100')->all();
  4. // 即时加载: SELECT * FROM customer LIMIT 100
  5. // SELECT * FROM order WHERE customer_id IN (1,2,...) AND subtotal>100
  6. $with = [
  7. //query就是SELECT * FROM order WHERE customer_id IN (1,2,...) query对象,然后可以随意的加andwhere 和 orwhere;
  8. 'orders' => function($query) {
  9. $query->andWhere('subtotal>100');
  10. },
  11. ];
  12. $customers = Customer::find()->limit(100)->with($with)->all();
逆关系
  1. # question:试验效果无效
  2. $user = Users::findOne(25);
  3. $orders = $user -> orders[0];
  4. echo $orders -> user === $user ? '相同':'不相同';
  5. //↑↑定义了相反的关联关系后,按道理返回的应该是相同;
  6. # @var $this Users
  7. return $this->hasMany(Orders::className(), ['user_id' => 'user_id']) -> inverseOf('user');
  8. //注意'user'为Orders类里面getUser;
JOIN 类型关联查询
  1. # Model @var $this Order;
  2. return $this->hasMany(Users::className(), ['user_id' => 'user_id']) -> select(['user_id','truename']) -> asArray();
  3. # controller 此处with里面的依旧是model里面的getter方法,默认是leftJoin
  4. $users = Orders::find() -> joinWith('user') -> limit(1) -> all();
  5. foreach($users as $user){
  6. $r = $user -> user; //每个订单下面的用户
  7. }
  8. // 连接多重关系
  9. // 找出24小时内注册客户包含书籍的订单
  10. // 每个with都要在model里面定义
  11. $orders = Order::find()->innerJoinWith([
  12. 'books',
  13. 'customer' => function ($query) {
  14. $query->where('customer.created_at > ' . (time() - 24 * 3600));
  15. }
  16. ])->all();
  17. // 连接嵌套关系:连接 books 表及其 author 列
  18. $orders = Order::find()->joinWith('books.author')->all();
  19. // 查找包括书籍的所有订单,但 "books" 表不使用即时加载
  20. $orders = Order::find()->innerJoinWith('books', false)->all();
  21. // 等价于:
  22. $orders = Order::find()->joinWith('books', false, 'INNER JOIN')->all();
  1. # 有时连接两个表时,需要在关联查询的 ON 部分指定额外条件。 这可以通过调用 yii\db\ActiveQuery::onCondition() 方法实现:
  2. # 也就是 on...and...语句
  3. class User extends ActiveRecord
  4. {
  5. public function getBooks()
  6. {
  7. return $this->hasMany(Item::className(), ['owner_id' => 'id'])->onCondition(['category_id' => 1]);
  8. }
  9. }
作用域
默认作用域
  1. # 在一个baseModel里面可以重写find
  2. # question 到时候试验一下吧
  3. public static function find()
  4. {
  5. return parent::find()->where(['deleted' => false]);
  6. }
自定义作用域
  1. namespace app\models;
  2. use yii\db\ActiveQuery;
  3. class CommentQuery extends ActiveQuery
  4. {
  5. public function active($state = true)
  6. {
  7. $this->andWhere(['active' => $state]);
  8. return $this;
  9. }
  10. }
  1. namespace app\models;
  2. use yii\db\ActiveRecord;
  3. class Comment extends ActiveRecord
  4. {
  5. /**
  6. * @inheritdoc
  7. * @return CommentQuery
  8. */
  9. public static function find()
  10. {
  11. return new CommentQuery(get_called_class());
  12. }
  13. }
读取默认值

如果没有该字段就以默认值代替;

  1. $customer = new Customer();
  2. $customer->loadDefaultValues(); // ... 渲染 $customer 的 HTML 表单 ...
DML操作

AR 提供以下方法插入、更新和删除与 AR 对象关联的那张表中的某一行:

AR 同时提供了一下静态方法,可以应用在与某 AR 类所关联的整张表上。 用这些方法的时候千万要小心,因为他们作用于整张表! 比如,deleteAll() 会删除掉表里所有的记录。

  1. // 插入新客户的记录
  2. $customer = new Customer();
  3. $customer->name = 'James';
  4. $customer->email = 'james@example.com';
  5. $customer->save(); // 等同于 $customer->insert();
  6. // 更新现有客户记录
  7. $customer = Customer::findOne($id);
  8. $customer->email = 'james@example.com';
  9. $customer->save(); // 等同于 $customer->update();
  10. // ↑↑ 注意此处的$customer如果是通过new获得的话就是插入,如果是通过find获得的话就是更新;
  11. // 删除已有客户记录
  12. $customer = Customer::findOne($id);
  13. $customer->delete();
  14. // 删除多个年龄大于20,性别为男(Male)的客户记录
  15. Customer::deleteAll('age > :age AND gender = :gender', [':age' => 20, ':gender' => 'M']);
  16. // 所有客户的age(年龄)字段加1:
  17. Customer::updateAllCounters(['age' => 1]);
数据输入与有效性验证

由于AR继承自yii\base\Model,所以它同样也支持Model的数据输入、验证等特性。
当你调用 save()、insert()、update() 这三个方法时,会自动调用yii\base\Model::validate()方法。如果验证失败,数据将不会保存进数据库。

  1. // 新建一条记录
  2. $model = new Customer;
  3. if ($model->load(Yii::$app->request->post()) && $model->save()) {
  4. // 获取用户输入的数据,验证并保存
  5. }
  6. // 更新主键为$id的AR
  7. $model = Customer::findOne($id);
  8. if ($model === null) {
  9. throw new NotFoundHttpException;
  10. }
  11. if ($model->load(Yii::$app->request->post()) && $model->save()) {
  12. // 获取用户输入的数据,验证并保存
  13. }

yii\base\Model::rules() 方法应返回一个由规则所组成的数组,每一个规则都呈现为以下这类格式的小数组:

  1. [
  2. // 必填项,用于指定那些模型特性需要通过此规则的验证。
  3. // 对于只有一个特性的情况,可以直接写特性名,而不必用数组包裹。
  4. ['attribute1', 'attribute2', ...],
  5. // 必填项,用于指定规则的类型。
  6. // 它可以是类名,验证器昵称,或者是验证方法的名称。
  7. 'validator',
  8. // 可选项,用于指定在场景(scenario)中,需要启用该规则
  9. // 若不提供,则代表该规则适用于所有场景
  10. // 若你需要提供除了某些特定场景以外的所有其他场景,你也可以配置 "except" 选项
  11. 'on' => ['scenario1', 'scenario2', ...],
  12. // 可选项,用于指定对该验证器对象的其他配置选项
  13. 'property1' => 'value1', 'property2' => 'value2', ...
  14. ]

对于每个规则,你至少需要指定该规则适用于哪些特性,以及本规则的类型是什么。你可以指定以下的规则类型之一:

  1. [
  2. # yii\validators\RequiredValidator
  3. [['email', 'password'], 'required'],
  4. # yii\validators\BooleanValidator 这是一个二选一的验证器,不仅仅是验证是否布尔值
  5. // 检查 "selected" 是否为 0 或 1,无视数据类型
  6. ['selected', 'boolean'],
  7. // 检查 "deleted" 是否为布尔类型,即 true 或 false,strict如果为true的话就严格在true和false之间验证;
  8. ['deleted', 'boolean', 'trueValue' => true, 'falseValue' => false, 'strict' => true],
  9. # yii\captcha\CaptchaValidator 用于验证验证码是否正确
  10. ['verificationCode', 'captcha'],
  11. # yii\validators\CompareValidator 比较验证器
  12. // 检查 "password" 特性的值是否与 "password_repeat" 的值相同,第三参数compareValue默认回来第一参数后面加上repeat
  13. ['password', 'compare'],
  14. // 检查年龄是否大于等于 30
  15. ['age', 'compare', 'compareValue' => 30, 'operator' => '>='],
  16. # yii\validators\DateValidator,基本上关于时间的验证能想到的都有,具体的去类里面看看;
  17. [['from', 'to'], 'date'],
  18. # yii\validators\DefaultValueValidator
  19. // 若 "age" 为空,则将其设为 null
  20. ['age', 'default', 'value' => null],
  21. // 若 "from" 和 "to" 为空,则分别给他们分配自今天起,3 天后和 6 天后的日期。
  22. [['from', 'to'], 'default', 'value' => function ($model, $attribute) {
  23. return date('Y-m-d', strtotime($attribute === 'to' ? '+3 days' '+6 days'));
  24. }]
  25. # yii\validators\NumberValidator
  26. // 检查 "salary" 是否为浮点数
  27. ['salary', 'double'],
  28. # yii\validators\EmailValidator
  29. // 检查 "email" 是否为有效的邮箱地址
  30. ['email', 'email'],
  31. # yii\validators\FileValidator
  32. // 检查 "primaryImage" 是否为 PNG, JPG 或 GIF 格式的上传图片。
  33. // 文件大小必须小于 1MB
  34. ['primaryImage', 'file', 'extensions' => ['png', 'jpg', 'gif'], 'maxSize' => 1024*1024*1024],
  35. # yii\validators\FilterValidator
  36. // trim 掉 "username" 和 "email" 输入
  37. [['username', 'email'], 'filter', 'filter' => 'trim', 'skipOnArray' => true],
  38. // 标准化 "phone" 输入
  39. ['phone', 'filter', 'filter' => function ($value) {
  40. // 在此处标准化输入的电话号码
  41. return $value;
  42. }],
  43. # yii\validators\ImageValidator
  44. // 检查 "primaryImage" 是否为适当尺寸的有效图片
  45. ['primaryImage', 'image', 'extensions' => 'png, jpg',
  46. 'minWidth' => 100, 'maxWidth' => 1000,
  47. 'minHeight' => 100, 'maxHeight' => 1000,
  48. ],
  49. # yii\validators\RangeValidator
  50. // 检查 "level" 是否为 1、2 或 3 中的一个
  51. ['level', 'in', 'range' => [1, 2, 3]],
  52. # yii\validators\NumberValidator
  53. // 检查 "age" 是否为整数
  54. ['age', 'integer'],
  55. // 检查 "salary" 是否为数字
  56. ['salary', 'number'],
  57. # yii\validators\RegularExpressionValidator
  58. // 检查 "username" 是否由字母开头,且只包含单词字符
  59. ['username', 'match', 'pattern' => '/^[a-z]\w*$/i']
  60. # yii\validators\SafeValidator
  61. // 标记 "description" 为安全特性
  62. ['description', 'safe'],
  63. # yii\validators\StringValidator
  64. // 检查 "username" 是否为长度 4 到 24 之间的字符串
  65. ['username', 'string', 'length' => [4, 24]],
  66. # yii\validators\FilterValidator
  67. // trim 掉 "username" 和 "email" 两侧的多余空格
  68. [['username', 'email'], 'trim'],
  69. # yii\validators\UniqueValidator
  70. // a1 需要在 "a1" 特性所代表的字段内唯一
  71. ['a1', 'unique'],
  72. // a1 需要唯一,但检验的是 a1 的值在字段 a2 中的唯一性
  73. ['a1', 'unique', 'targetAttribute' => 'a2'],
  74. // a1 和 a2 的组合需要唯一,且它们都能收到错误提示
  75. [['a1', 'a2'], 'unique', 'targetAttribute' => ['a1', 'a2']],
  76. // a1 和 a2 的组合需要唯一,只有 a1 能接收错误提示
  77. ['a1', 'unique', 'targetAttribute' => ['a1', 'a2']],
  78. // 通过同时在 a2 和 a3 字段中检查 a2 和 a3 的值来确定 a1 的唯一性
  79. ['a1', 'unique', 'targetAttribute' => ['a2', 'a1' => 'a3']],
  80. // 检查 "website" 是否为有效的 URL。若没有 URI 方案,则给 "website" 特性加 "http://" 前缀
  81. ['website', 'url', 'defaultScheme' => 'http'],
  82. ]
验证提示信息改为中文
  1. array('name,type', 'required', 'message' => '{attribute}不能为空') //利用{attribute}可以动态获取属性名称

视图

  1. # Controller
  2. public $layout = 'post'; //本控制器视图用的布局都是post
  3. public function actionView($id)
  4. {
  5. $model = Post::findOne($id);
  6. if ($model === null) {
  7. throw new NotFoundHttpException;
  8. }
  9. // 渲染一个名称为"view"的视图并使用布局
  10. return $this->render('view', [
  11. 'model' => $model,
  12. ]);
  13. }
  1. <?php
  2. /* @var $this \yii\web\View */
  3. /* @var $content string */
  4. use yii\helpers\Html;
  5. use yii\bootstrap\Nav;
  6. use yii\bootstrap\NavBar;
  7. use yii\widgets\Breadcrumbs;
  8. use app\assets\AppAsset;
  9. AppAsset::register($this);
  10. ?>
  11. <?php $this->beginPage() ?>
  12. <!DOCTYPE html>
  13. <html lang="<?= Yii::$app->language ?>">
  14. <head>
  15. <meta charset="<?= Yii::$app->charset ?>">
  16. <meta name="viewport" content="width=device-width, initial-scale=1">
  17. <?= Html::csrfMetaTags() ?>
  18. <title><?= Html::encode($this->title) ?></title>
  19. <?php $this->head() ?>
  20. </head>
  21. <body>
  22. <?php $this->beginBody() ?>
  23. <div class="wrap">
  24. <?php
  25. NavBar::begin([
  26. 'brandLabel' => 'My Company',
  27. 'brandUrl' => Yii::$app->homeUrl,
  28. 'options' => [
  29. 'class' => 'navbar-inverse navbar-fixed-top',
  30. ],
  31. ]);
  32. echo Nav::widget([
  33. 'options' => ['class' => 'navbar-nav navbar-right'],
  34. 'items' => [
  35. ['label' => 'Home', 'url' => ['/site/index']],
  36. ['label' => 'About', 'url' => ['/site/about']],
  37. ['label' => 'Contact', 'url' => ['/site/contact']],
  38. Yii::$app->user->isGuest ?
  39. ['label' => 'Login', 'url' => ['/site/login']] :
  40. [
  41. 'label' => 'Logout (' . Yii::$app->user->identity->username . ')',
  42. 'url' => ['/site/logout'],
  43. 'linkOptions' => ['data-method' => 'post']
  44. ],
  45. ],
  46. ]);
  47. NavBar::end();
  48. ?>
  49. <div class="container">
  50. <?= Breadcrumbs::widget([
  51. 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [],
  52. ]) ?>
  53. <?= $content ?>
  54. </div>
  55. </div>
  56. <footer class="footer">
  57. <div class="container">
  58. <p class="pull-left">&copy; My Company <?= date('Y') ?></p>
  59. <p class="pull-right"><?= Yii::powered() ?></p>
  60. </div>
  61. </footer>
  62. <?php $this->endBody() ?>
  63. </body>
  64. </html>
  65. <?php $this->endPage() ?>

数据块

  1. ...
  2. <?php $this->beginBlock('block1'); ?>
  3. ...content of block1...
  4. <?php $this->endBlock(); ?>
  5. ...
  6. <?php $this->beginBlock('block3'); ?>
  7. ...content of block3...
  8. <?php $this->endBlock(); ?>
  1. # 用子视图中的数据块覆盖
  2. <?php if (isset($this->blocks['block1'])): ?>
  3. <?= $this->blocks['block1'] ?>
  4. <?php else: ?>
  5. ... default content for block1 ...
  6. <?php endif; ?>

元信息

  1. <title><?= Html::encode($this->title) ?></title>
  2. <?php
  3. $this->registerMetaTag(['name' => 'keywords', 'content' => 'yii, framework, php']);
  4. $this->registerMetaTag(['name' => 'description', 'content' => 'This website is about funny raccoons.'], 'description');
  5. $this->registerLinkTag([
  6. 'title' => 'Live News for Yii',
  7. 'rel' => 'alternate',
  8. 'type' => 'application/rss+xml',
  9. 'href' => 'http://www.yiiframework.com/rss.xml/',
  10. ]);
  11. //生成如下link标签: <link title="Live News for Yii" rel="alternate" type="application/rss+xml" href="http://www.yiiframework.com/rss.xml/">
  12. ?>

嵌套布局

  1. # 子布局,类似数据块,数据块都是写在模板里面
  2. <?php $this->beginContent('@app/views/layouts/base.php'); ?>
  3. ...child layout content here...
  4. <?php $this->endContent(); ?>

在视图中渲染另外一个视图

  1. <?= $this->render('_overview') ?> //在本视图中再渲染一个视图

视图与视图间传递数据

  1. # 推送,必须是键值对,然后以对应的键名取出来
  2. echo $this->render('report', [
  3. 'foo' => 1,
  4. 'bar' => 2,
  5. ]);
  6. # 拉取

视图间共享数据

yii\base\View视图组件提供yii\base\View::params参数属性来让不同视图共享数据。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注