tp剩余未验证内容-7

时间:2024-05-25 20:33:38

bash脚本中 的 set -e表示 exit immediately if a simple command returns a non-zero value.主要是为了防止错误被忽略.会被立即退出, 但是最好在开发结束后, 删除这个指令, 以免留下隐患.

有四种命令提示符, 有PS1, 自然就有 PS2 PS3 PS4, 分别表示 PS2即 在后续的命令下一行的提示符(continu'ation interactive prompt), PS3是在select选项时 的提示符, PS4是调试时的提示符.

shell中要进行算术运算, 有5种方法: (因为默认的算术运算符号+-*/ 都不会被直接当做运算符, 而只是当做普通字符来看待, 所以需要特殊处理)

  • 使用算术扩展: $(( ... )); $[...]
  • 使用外部命令expr expr 1 + 2 注意有空格(特殊符号要用* 转义)
  • 使用内部命令: let 1+2 或 declare -i c=1+2

    外部命令,就是外部存在的 可以执行的文件, 可以用 which命令看得到的, 根shell无关的; 所谓内部命令,就是 shell本身提供的命令(函数), 用which是查看不到没有的.

shell的调试?

  • 直接用 sh -x ./foo.sh 命令, 其中 -x就是调试的意思 还有其他一些选项 : sh -n只是检查文件是否有语法错误; sh -c "..." 表示文件从字符串中读取.
  • 或者直接将调试选项写在sh文件中的 #!bin/bash -xv???
  • 或者在文件中, 在要开始调试的 位置 写上 set -x 但是要注意, 只有set -x之后的命令才会调试, 这个之前的命令不会被调试.
  • 所谓调试就是 会将每一次/ 每一步 step by step 执行的语句 先显示出来, 并且会将执行语句中的变量用当前值 替换; 然后输出命令执行的结果. 而且执行语句前加上+号

$_SERVER['HTTP_REFERER']为什么为空?

  • referer的正确拼写是 referrer, 所以前者其实是一种历史的错误遗留

  • http_referer是header请求头的一部分, 通常会带在向web服务器发生的信息中, 告诉服务器我是从 哪个页面链接而来的. 可以给服务器一些额外的信息, 便于其他一些处理.

  • http_refer完全依赖于浏览器自身, 有的浏览器是没有设置这个变量的. 你就得不到它.

  • 但是, 在很多情况下http_referer会无效, null. 比如你 直接访问一个页面的时候, 或从收藏夹中访问页面的时候,

    所以一般来说,只有通过 <a> 超链接 </a> 或者: location.href=...跳转, 以及 POST 或 GET 表单 提交访问的页面, $_SERVER['HTTP_REFERER'] 才有效。

  • 由于 $_SERVER['HTTP_REFERER'] 对 POST 表单访问也是有效的,因此在表单数据处理页面 **一定程度上 ** 可以通过校验 $_SERVER['HTTP_REFERER'] 来防止表单数据的恶意提交。但该方法并不能保证表单数据的绝对正确,即对表单数据的真实性检测并不能完全依赖于 $_SERVER['HTTP_REFERER'] 。

========================

所以, 在tp中, 操作成功或失败的提示 跳转是用 success和error. $this->success/errror('跳转提示信息', 跳转的地址_默认是http_referer, 跳转等待时间)

使用js 使html中的数字递减?

  • 一个页面中, 包含多个head, body, html等标签虽然不规范, 但是仍然可以解析显示的.
  • js获取一个元素的属性?
  • setInterval和clearInterval?
  • 快速调用一个函数的方法.
<script type="text/javascript">
(function(){
var wait = document.getElementById('wait'),href = document.getElementById('href').href;
var interval = setInterval(function(){
var time = --wait.innerHTML;
if(time <= 0) {
location.href = href;
clearInterval(interval);
};
}, 1000);
})();
</script>

关于tp?

tp的文件上传 在"专题"中.

tp使用 "类" 的思想: 先new一个 类对象, 然后让 类对象与实际要操作(上传)的文件相关联, 然后通过操作这个 已经关联了的类对象实现对该文件的各种操作.

  • 实际上,这也是 "类"的一个基本思想. 一个类对象(泛泛的), 必须跟具体的实物(生活中的个体) 相关联. (类的对象, 跟具体的实物/事物相关联的 过程, 正是所谓的 类对象的实例化/初始化). 实例化后, 这个类对象 就代表该具体的 人/事物了, 对该对象的操作, 就是对该人/事物的操作!
  • 如果类对象, 没有跟具体的实物相关联, 那么它实际上是没有意义的, 也是没有用处的. 比如一张 银行卡"对象", 在没有跟某个具体的人(储户)相关联的时候, 即没有"开卡"的时候, 这张卡其实是没有用的,你随便怎样处置都是可以的. 但是如果你有一张十万元的卡, 跟你的储存信息/存储资金 相关联了, 如果你丢失了, 又是怎样的一种情景呢?
  • 而上面所说的, 类(对象)没有跟具体的 事物相关联, 只是规定了这个类 可以具有的功能和特征, 这种类, 其实也是有用的, 它就是只是作为一种协议, 一种规定, 就是一种 "接口", "接口"都是抽象的, 即泛泛而论的东西, 不能跟具体的某个 实物相关联, 因此, 它是接口, 不能被 "实例化""初始化"

tp中的array数组, 是应用得最广泛的. 可以说凡是能用 "字符串"的地方, 几乎都可以用数组来 表示, 用数组来操作, 而且都推荐用数组, (因为数组更安全??)


tp的orm就是, 对数据库的操作, 不再用原始的/原生的 mysql/mysqli函数(面向过程)来处理, 而是用 模型(跟数据库的表相对应)的类/对象来操作, 用对象的方法比如find,select add save等进行增删改查的操作.

其中, add和save方法, 需要先创建 插入和更新的 数据对象$data(实际上就是要插入/更新到表中的 记录)

这种创建数据对象的方法, 有两种, 一是 create()方法, 一是data()方法. 两个方法的相同点是: 都可以/都支持多种数据来源, 包括从 数组, 其他数据对象甚至 普通对象来创建; 不同点是: create的功能更强大, 不但支持创建的数据对象的自动验证和自动完成($_validate和$_auto), 而且还可以 自动地从 $_POST数组创建数据对象.

  • 创建的数据对象是保持在 内存中, 并没有马上写入到数据库中, 要直到使用 add()方法和save()方法才会写入数据库.
  • 所以, create的数据对象, 你是可以直接显示dump出它的内容的; 而且还可以继续修改.

在进行数据库相关的操作时, 一定要首先设置 数据库配置.

  • 因为你在创建 模型对象 $User = M('user') 的时候, 就需要数据库配置, 如果没有配置/没有正确配置, 就会报错.
  • 首先去加载 Frame\Library\Think\Db.class.php, 执行第一个静态方法去获取数据库类的实例static public function getInstance($config=array()) 参数就是$config数据库连接配置!
  • tp错误的统一输出形式用 : E(L('_NO_DB_DRIVER_') . ':'. $class), E函数的原型是 E($msg, $code); 所以所有的错误提示内容, 都要放在整个 E() 函数的括号内 错误函数是 抛出了一个 异常 throw new Exception($msg, $code); 所以: E函数后的所有内容 都将 停止执行, 直接从 E()函数处退出了, 而且是调用 统一的 异常输出模板.
  • 注意配置的下标名称是: 由于DB本身就有 '数据库' 的意思, 数据库的名称 配置项 是 'DB_NAME' 不是DB_DATABASE, 数据库密码是DB_PWD, 不是 DB_PASSWD.
  • 数据库类型要明确写成, 因为在 convention.php中, 没有默认的数据库类型配置

#### tp表单在提交的时候, 有两种方式过滤字段,
一是使用 field函数,( 要注意, field方法没有复数, 所以其参数也是 **一个 **字符串. 另外field是指定 接收的/生效的字段, 不是 将被过滤被丢弃的字段 ) 然后用create()创建,
二是配置 insertFields, updateFields两个的值,
**即使设置了表单 的字段映射, 但是在 后面的所有 连贯操作的 field方法中, 表示 参数的 字段都 应该是实际的数据表字段, 而不是 字段映射, 不是表单中的 字段域, 否则 当定义了字段映射时, 又使用 filed('表单字段'), 那么就会出现 create的数据对象 为空 empty的 错误!

要注意, 如果要设置 $insertFields 和 $updateFields的值, 以及要实现自动验证和自动完成, 都要 创


建 自定义的 模型类, 不能直接使用标准 的模型类的基类.

甚至于, 即使一个大型的很多文件 需要同时编辑的场合下, 用一个 vim窗口界面都是可以胜任的, 首先它的打开速度很快, 其次可以分成多个(比如4~5)个子窗口来同时编辑多个文件, 最重要的是, 每一个子窗口都可以 保存多个 曾经打开的 文件的缓冲, 在每一个子窗口都可以使用 ctrl+^ 来切换缓冲的文件.

tp多个配置文件?

  • 你可以将所有的配置杂七杂八地放到同一个配置文件中, 比如/Application/Home/Conf/config.php中, 但是如果配置比较多, 比较繁杂的话,就会 显得比较凌乱. 所以将配置分门别类的放在各自的/ 单独的 配置文件, 然后 "包含它们" 是一个比较好的.
  • 你也不能单单的把 某个方面的 配置文件 "放到" '扔到' conf目录下就行, 那样的话, tp也不会知道 你的文件就是配置文件啊, 所以需要给 tp "说 一声, 告知一下 注册一下", 就是要在 "主"配置文件中 说一下: 比如 'load_ext_config' => 'mylang'
  • 自己扩展的配置文件,比如mylang.php是放在跟你包含 说明的配置文件相同目录下, 比如 在 Home/Conf/config.php中配置load_ext_config, 那么扩展配置也就放在Home/Conf中, 而且扩展配置 说明中 不要带.php扩展名

**tp的配置尽量用 小写字母, 因为不管大写还是小写, 最后都要转变为小写. 虽然为了好看,"推荐"用小写. 但是在实际开发中, 一切都是 以 "效率" 为最高原则的. **

tp如何配置自己的 语言文件?? 参考https://www.jb51.net/article/47624.htm http://www.cnblogs.com/yuwensong/p/4156383.html

这个还是比较复杂的, 通常是不需要的.如果确实要这样做, 步骤是:(但是好像有错误???)

1.在/App/Home/Conf/config.php配置中, 追加

    'LANG_SWITCH_ON'     =>     true,    //开启语言包功能
'LANG_AUTO_DETECT' => true, // 自动侦测语言
'DEFAULT_LANG' => 'zh-cn', // 默认语言
'LANG_LIST' => 'en-us,zh-cn,zh-tw', //必须写可允许的语言列表
'VAR_LANGUAGE' => 'l', // 默认语言切换变量
2.在Home/Conf目录中创建一个php文件, 比如: tag.php 内容如下

array('CheckLang')
);
```

3.把框架中的 Extend/Behavior/CheckLangBehavior.class.php 文件复制到 Home/lib/Behavior/中(完整版的thinkphp包才有,没有的话请自行创建)
4.然后就是 创建对应的语言项文件了, 在 /App/Home/下创建对应的三个语言文件夹: zh-cn, en-us, zh-tw. 然后再在这些语言目录中创建对应的语言文件, 文件名必须设置为common.php??
5.最后就是 引用/使用 语言配置了, 在模板文件中, 使用 `{$Think.lang.语言项}` 在后台控制器中, 用L()方法来引用.

```

==================================================================

#### 如果dump一个对象的话, 将输出这个对象的所有内容, 包括对象的 所有成员变量和所有的成员方法.

### 关于数据对象data和模型对象$User的区别?
- $User是 通过M, D等方法创建的模型对象. 有两个方面的作用, 一个是 关于模型的所有方法操作, 如add, select, save, delete等都必须通过这个对象完成; 另一个作用是, 其本身也可以 保持/保存 一些"数据", (这些数据不是指 模型类本身定义时所包含的成员等, 而是指 包含 将要写入到数据库表中 "记录"内容).
- 数据对象, 是指 模型类$User 所包含的 表记录 数据. $User获得 表记录数据有两种途径, 使用 data()方法, 或者使用 create()方法.
一方面, 你可以直接 echo出 对象$User所包含的表记录字段, 比如: `echo $User->name`;
另一方面, 你还可以接收 create方法的返回对象 `$var_data = $User->create()` , 这样你就可以很直观地dump出 $var_data 查看数据了

#### 数据表的select操作, 不只是可以选择记录, 而且可以完成复杂的 字段运算等
where子句,不但能根据条件选择筛选记录, 而且 在 id自增的时候 可以用来选择最前/最后/中间 N条记录: `... where id > Max(id)....`
通常来说, where, limit等的操作耗时 比order的耗时 要小, 应该尽量避免 order操作??
**在非mysql的选择子句中,最前面N条记录 可以用 top子句 , 但是 mysql没有top子句! 只能使用limit子句.** 参考 `http://www.cnblogs.com/freeliver54/archive/2008/07/23/1249232.html`
- limit子句的格式是: limit 因为是offset偏移, 所以总是比起始记录位置小1. 比如: 选择从第10条到20条的记录, 应该是: `limit 9, 11` 从第10条开始的共10条记录: `limit 9, 10`
- mysql没有直接选取 最后N条 的子句.**

male和female不只是指人, 还可以用来指 雄性动物或 雄性植物(雄株) . man和woman通常用来指**成年**男人和女人. 也就是说 male包括 man和boy. female的fe.

#### create在 创建数据对象时, 会 验证数据源的合法性(会自动过滤 数据表中没有的字段!). 所以即使表单中有 多余的/不是数据表中的字段信息, 也不用管, 因为 非数据表字段会自动过滤/丢弃,不会出现在 创建的数据对象中.
- 模型类对表单中的 字段映射: 目的, 因为默认的在创建数据对象时, 是要求 表单中的 字段name要和 数据表中的字段名称 相同, 否则会报错. 但是那样会在表单中暴露数据表的字段,引起安全问题. 所以 通过字段映射, 让在表单中的字段名称 => 映射到 数据表中的字段名. (这样即实现了 从表单到 数据表的数据对象创建, 又隐藏了数据表自动名, 多了一点安全保护).
- 字段映射的方式是: 在自定义模型类 中添加`protected $_map=array('表单字段名' => '数据表字段名');` 注意是把 表单字段 映射为 数据表字段, 不可能是反过来吧,本身你就要隐藏数据表字段呢, 你难道还想 主动暴露到表单中来吗?

- 使用字段映射: 即使使用了字段映射, 并不会 自动 影响 查询结果, 查询结果中的 记录中的 字段名称仍然是 原来的数据表中的字段名称, 不会是 "表单"字段名. 除非你在配置中设置了 `read_data_map' => true,` 或者使用 D('user') -> parseFieldsMap($data_result)方法手动 转换.

- 在`$User->create()` 后, 里面的数据对象就已经被 映射了, 即原来表单中的字段就已经自动转换为数据表中的字段了,(这个是create所作的工作之一),所以接下来就可以用 连贯操作 add了 .

#### 字符为空 和 not null 是不同的! 当表单中的input域(比如姓名) 没有填入内容的时候, 传递到$_post的是 ""空字符串, 不是not null. 所以 它是合法的, 会被add添加到数据表中.

```
$_POST是:
array (size=2)
'username' => string '' (length=0) //这里明确给出了是: string , 而且是 '', 不是 null!
'gender' => string 'm' (length=1)
```

#### 在数据表中, 字段field也叫做 column列. 在定义表/修改表结构的时候, 格式是: col_name colomn_specification/col_definition 即 列名 然后是对列的定义/说明/描述 (多个定义单词直接用空格分隔).

===============================================================

#### 要想一次性的关闭多个 已经打开的缓冲区, (而不用一个一个地去关闭), 使用 冒号命令: `bufdo bd`其中 bufdo是指 针对所有的 缓冲区执行的命令.

#### 关于sql语句中 各子句的执行顺序? 参考 `主要是这个: http://www.cnblogs.com/Qian123/p/5669259.html 和http://www.cnblogs.com/Qian123/p/5669259.html` 明确了sql语句的执行过程, 你对数据库的执行过程和原理就会更深入, 也会避免一些使用上的错误, 比如:
- sql语句中, 任何一个 "关键字(单词/词组)"都是一个子句, 包括: from -> on -> join -> where -> group by -> having -> select -> disctinct -> order by-> limit
- select子句虽然在最前面, 但并不是第一个执行, 它是在第8步才执行的. 而只有在select 子句中 才能定义 字段(列)的alias,(from子句中可以定义表的别名) 所以在第8步select子句之前都是不能使用 列的别名的, 只有在这之后, 即第9步distinct 第10步order by子句中使用列的别名
- 最先执行的总是 from子句, 最后执行的总是 limit子句. 所以 在from子句中定义 的表的 alias别名, 可以在 其后的(从第2步后)所有子句中都可以使用, 包括随后的where子句中就可以使用表的别名.
-  mysql中 的所有别名, 包括 表的别名, 和 字段的别名 都可以加上 as, 也可以省略 as.
- 通常每一步子句 得到的是一个 虚拟表virtual table(即虚表VT), 从 VT1~ VT9, 但是在第10步, 即order by子句, 返回的并不是一个 虚表vt, 而是 一个游标 即VC(virtual cursor). 所以凡是包含 order by子句的 查询句都不能作为 "表的表达式)(即在 凡是需要一个 表的表达式的地方, 都不能包含order by子句) 表的表达式其实就是 一个虚表vt.
- distinct子句 的原理 是 在内存中, 利用一个 临时表来 得到一个 vt的

- 要理解select子句的过程: 查询/选择 结果字段, 其实并不是直接拿着 这些要显示的字段到数据表里面去"挑选"记录.实际上, 在select之前, 已经做了大量的工作, 已经经过/得到了多个 步骤的 VT虚表的迭代过程了. 已经准备好了 查询结果的(包含所有字段)的 多个记录了. select子句的作用 **仅仅**是 挑选要显示/要返回的 部分字段(但是其他字段其实还是存在的, 因为即使在 其后的 distinct/order by 等步骤中, 虚表VT10/11 其实还是一直存在的, 在内存中), 所以在 select子句后, 还可以使用 order by子句按 非select字段进行排序的.
- 在order by子句前, 整个过程的vt都是 **无序**的. 只有当你确实需要有序的, 需要排序的结果集时, 才用order by. order by很耗资源和时间, 要慎用.
- mysql中的统计函数, 比如max, min, sum, avg, 也叫 分组函数(group functions). 也就是说, 只有在 执行了group by子句后, 其他子句才能使用: 即在第5步后 才能使用 分组(统计)函数. 而where子句是在 group by子句之前执行的, 所以 在 where 子句中, 就不能使用 group子句中才能使用的 统计函数. 比如 `where score>=average(score)?? ` 就会报错: "invalid use of group functins"

- 分组后, 在分组记录中, 我们可以 select出关于分组的统计信息, 也可以select 原数据表中的非统计信息(即普通字段信息), 虽然分组操作的目的通常是要 select出统计信息. 如果分组后 select的是 非统计字段信息,则 在第9 步(select步骤)总是显示的/保留的是 相同分组中 排在最前面的那个记录的 字段信息. (但是 在分组后所得的虚拟表VT(第5步时)分组中的 其他记录/信息仍然是保留的. 是按分组字段的不同 挨着挨着排列的(类似于excel中的分组), 所以, 一直到第8步select的时候 还可以用 count, max等统计函数对所有 记录 和 相关字段进行统计 )

#### 关于分组后的显示结果?

```
+------+-------------+-------+-------+---------------+---------+---------+------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+-------+---------------+---------+---------+------+------+----------------------------------------------+
| 1 | SIMPLE | user | range | PRIMARY | PRIMARY | 4 | NULL | 7 | Using where; Using temporary; Using filesort |
+------+-------------+-------+-------+---------------+---------+---------+------+------+----------------------------------------------+
1 row in set (0.00 sec)
```

关于explain中的说明?
- type是指查询的范围, 是整个记录ALL, 还是有条件/范围的, 通常where子句属于 type=range的. 如果查询条件中包含了主键 那么在explain中会显示primary key. extra是说会用到哪些 "额外" 操作. group by分组, 会使用到临时表temporary和其结果会自动 按分组字段 升序排序??(隐含一个 order by group_column asc)?? 所以explain中, 会有 using filesort. **凡是有 (或者是 隐含有) order by 排序的操作, 都会有 using filesort的 执行计划. 比如 group by的分组操作, 就暗含了 分组后的记录 按 分组字段 asc 升序排列的 操作.**

### 在要实现一些比较 复杂的 查询功能时, 都会使用 "子查询". 子查询主要有两个作用: 得到的 可以是一个 表的表达式, 用来作为中间表/临时表, 也可以得到的是一个 数值, 用在where等子句中. 所以, "子查询"的使用还是 很多的!

#### 当要求id最大值时, 慎用`select Max(id) ...` 而是用索引来做, 比如: 创建id的索引(`show index from table_name`), 然后用 `select... order by id desc limit 1` 来代替, 这样效率更高

- 在用group by分组后, 可以使用 分组函数(统计函数)比如max, min, sum等 确实能够 对每一组的数据 进行 最大值等的统计, 而不会只针对所有记录进行统计. 因为select子句在 group by子句后执行.
- 但是 group by和max等统计函数一起使用时要注意: 如果包含了非统计数据(比如普通字段的值), 由于分组后, 总是取第一条记录的普通字段的值,这样 当第一条记录的普通字段值和该分组的最大值等统计值一起 并列组成一条记录时, 就可能发生误解,跟实际情况不符. 比如统计分组中的最大值, 但是这个最大值并不一定就是 第一条记录的用户 正好具有该最大值.
此时有两种解决方案, 参考: `https://blog.****.net/john_hongming/article/details/42742965`
一是, 先将原来的表 按照要求Max/min的字段 进行排序, 升序或降序, 反正要保证 出现/保留在 分组里面的" 包含普通字段的那条记录" 要在 第一个记录位置就好. 得到一个 (中间表/表的表达式) 然后对这个中间表进行 分组.
二是: 使用where in ...

```
MariaDB [test]> select * from user where score in (select max(score) from user group by gender);
+------------+-----------+--------+-------+
| id | name | gender | score |
+------------+-----------+--------+-------+
| 0000000002 | 孙徐连 | w | 98 |
| 0000000010 | 王的徐 | m | 97 |
+------------+-----------+--------+-------+
2 rows in set (0.00 sec)

MariaDB [test]>
```

#### 对mysql而言, 当创建了主键后, (最常用的是id), 会自动地给 主键创建 索引 (所以, 不必重复地 去创建索引). `show index from user; `
- 给数据表创建主键时, 要注意对主键的设置加以程序安全性的保护, 因为一方面 增删改查主要就是根据主键来进行的; 另一方面,主键也 比较容易暴露泄露 数据库的信息.
数据表的 索引的创建很方便, 很简单, 不会给数据表带来负担. 但是, 索引在查询方面 可以带来很大的速度提升. 所以, 在实际项目中, 通常应该 给 要查询的 字段 (不只是主键字段) 比如'name'字段创建索引. 另外, 在 可能出错, 根据某个字段进行 排错时的字段 也可以给它创建索引.

#### "every derived(派生的/继承的/衍生的/导出的...) table must have its own alias" 就是说, 在 mysql中, 任何 中间表(表的表达式)/临时表都必须有一个 别名,而不管你会不会用到这个别名.
- 但并不是所有的 中间查询/临时查询/放在括号中的查询, 都要用 表的别名. 这个只是针对 需要 "表"的时候, 只是在 derived **table, 是table** , 只是在 from子句 后面的中间表, 才需要用 别名. 而在 where子句 是不需要表 的别名的! 因为 where子句中 只是需要一个 数据/数值/集合,用来满足where的 >, in, between等表达式而已.

#### having 和where的区别?
- 两者在语法上的写法是一样的, 都是条件筛选, 只不过where是对 表的 "列"进行筛选, 因此where表达式中只能 出现表的列字段, 而having是对 查询结果进行筛选.
- having可以单独使用, 不一定总是 必须 跟 group by一起使用!
- having 是对 (可以分组 也可能不分组 group by ->) select之后的结果 进行条件筛选. 所以 , 可以使用 select子句中的 所有别名, 包括字段运算后的别名.
- 在select语句中, 字段被 看成是 "变量" , 所以 可以对字段进行 算术运算的. 比如 查找成绩在90分以上的记录: `select id, name as '姓名', score-60 as chazhi from score having chazi>30; `
??? 难道having子句 是在 select子句后执行的??

```
MariaDB [test]> select id, name as '姓名', score-60 as chazi from user where chazi>30;
ERROR 1054 (42S22): Unknown column 'chazi' in 'where clause'
// 很明显, where只是针对 字段来判断的, 如果是select中的 运算表达式, 则会报错, "未知的字段"

MariaDB [test]> select id, name as '姓名', score-60 as chazi from user having chazi>30;
+------------+-----------+-------+
| id | 姓名 | chazi |
+------------+-----------+-------+
| 0000000001 | 孙以的 | 34 |
| 0000000002 | 孙徐连 | 38 |
| 0000000010 | 王的徐 | 37 |
+------------+-----------+-------+
3 rows in set (0.00 sec)

MariaDB [test]>

```

#### group by和order by的一起使用?
- 要求, order by中的字段, 必须是 出现在group by子句clause中.
- order by子句必须放在 group by子句的后面
- group by默认也要进行排序, 多个字段进行分组或排序时, 字段顺序必须固定, 不是随便写的.

===================================

#### 终端terminal shell是一个集成环境, 在里面运行的任何程序包括 vim, mysql, 等都是子程序, 都可以使用 shell统一的 菜单/快捷键设置操作, 比如 复制/粘贴等. 消除其他行, 到最顶端, 使用 ctrl_L

====================================

tp的功能也不是尽善尽美(实际上世上也没有尽善尽美的东西吧), 还有一些bug的.

在 项目App/Runtime/Home/目录下的 那些php文件, 实际上就是 View目录下的 "模板" 的编译结果文件(所谓编译, 就是将模板中的php代码 解析成普通的html后)

### 关于布局模板
- (模板)布局layout是tp的功能, 实现布局的方式是用 模板, 这个模板叫" 布局模板" . 不管哪一个框架的模板布局, 还是很有用的. 它是生成 基本框架都相同的多个页面的一种快速方法: 把多个页面中, 相同的要素(结构)比如页面的头部, 菜单栏, 页脚等内容是基本相同的, 提取出来, 然后多个页面中只有 主体内容 不同的部分进行组装. 没有必要每一个页面都完整的写一遍, 这个正是 符合软件 "结构化/模块化+重用" 的思想.

- 布局模板的实现有三种方式, 其中第二种方式, 是使用 layout标签, 这个标签的使用方法 ,跟其他html标签一样, 也是指定相应的属性就好了
layout标签不需要任何配置;
layout标签的属性有 name(使用哪个布局模板), 和 replace(布局模板中的替换字符串)
在 **头部增加**(好像不一定是要在 head中, 在body中定义也是可以的! 而且 layout标签甚至可以在body内容的最下面/最后面书写都是可以的. 但是一定要在配置中, 关闭 LAYOUT_ON设置,否则 布局不会成功, 不会应用 布局模板!!! ):` `
使用了layout标签后, 同样的, 是把 当前模板文件的内容 替换到 布局模板中的 {__REPLACE__}

- 使用include标签 中的 layout标签, 可以实现模板标签的嵌套.

- 在convention.php配置中, 关于布局(模板) 的配置 有3个:

```
'TMPL_LAYOUT_ITEM' => '{__CONTENT__}', // 布局模板的内容替换标识
'LAYOUT_ON' => false, // 是否启用布局
'LAYOUT_NAME' => 'layout',
// 当前布局名称 默认为layout
```

- 这个是整体/全局配置, 对整个项目中的所有模板文件 都有效的.
但是, 是可以来调节的,并不是说, 只要开启layout_on=>true后, 就必须/不得不 使用布局功能了. 实际上, 即使开启了layout功能后, 仍然有两者方法来关闭: 一是 在 需要 关闭的**模板**页面中, 加上 `{_ _NOLAYOUT_ _ } ` ; 二是 在 **控制器的操作中** 使用 全局函数layout(是框架中的functions.php文件中的函数), 比如 `layout(true), layout(false)禁用布局功能, layout('layout/new_layout')` 而且使用全局函数layout时是不需要开启` layout_on=>true` 这个配置的.

#### 要注意, 布局模板的位置默认的 跟普通模板文件的位置一样, 即布局模板 文件是: /AppName/Home/View/layout.html, 注意默认的是View目录下的 layout.html, 而如果有的配置设置了 模板的子目录位置时, 就要重新指定 "layout_name" 了.

###