ThinkPHP3.2.3完全开发手册


序言 手册阅读须知:本手册仅针对ThinkPHP3.2.3版本,尽管3.2版本大多数功能 都通用,但我们还是建议你把手册里面的特性使用在3.2.3版本(可以使用左 右键( <-- 和 --> )翻页阅读, CTRL+F 全屏阅读) 版权申明 发布本资料须遵守开放出版许可协议 1.0 或者更新版本。 未经版权所有者明确授权,禁止发行本文档及其被实质上修改的版本。 未经版权所有者事先授权,禁止将此作品及其衍生作品以标准(纸质)书籍形式 发行。 如果有兴趣再发行或再版本手册的全部或部分内容,不论修改过与否,或者有任 何问题,请联系版权所有者 thinkphp@qq.com。 对ThinkPHP有任何疑问或者建议,请进入官方讨论区 [ http://www.thinkphp.cn/topic ] 发布相关讨论。 有关ThinkPHP项目及本文档的最新资料,请及时访问ThinkPHP项目主站 http://www.thinkphp.cn。 本文档的版权归ThinkPHP文档小组所有,本文档及其描述的内容受有关法律的 版权保护,对本文档内容的任何形式的非法复制,泄露或散布,将导致相应的法 律责任。 捐赠我们 T hinkPHP一直在致力于简化企业和个人的WEB应用开发,您的帮助是对T hinkPHP一直在致力于简化企业和个人的WEB应用开发,您的帮助是对 我们最大的支持和动力!我们最大的支持和动力! 我们的团队9年来一直在坚持不懈地努力,并坚持开源和免费提供使用,帮助开 发人员更加方便的进行WEB应用的快速开发,如果您对我们的成果表示认同并 且觉得对你有所帮助我们愿意接受来自各方面的捐赠^_^。 ThinkPHP3.2.3完全开发手册 - 3 -© 本文档使用 看云 构建 用手机扫描进行支付宝捐赠 (查看捐赠列表) 基础 ThinkPHP是一个快速、简单的基于MVC和面向对象的轻量级PHP开发框架, 遵循Apache2开源协议发布,从诞生以来一直秉承简洁实用的设计原则,在保 持出色的性能和至简的代码的同时,尤其注重开发体验和易用性,并且拥有众多 的原创功能和特性,为WEB应用开发提供了强有力的支持。 3.2版本则在原来的基础上进行一些架构的调整,引入了命名空间支持和模块化 的完善,为大型应用和模块化开发提供了更多的便利。 3.2.3 主要更新 数据库驱动完全用PDO重写; 支持通用insertAll方法; 改进参数绑定机制; 主从分布式数据库连接改进; 对Mongo的支持更加完善; 模型类的诸多增强和改进; ThinkPHP3.2.3完全开发手册 - 4 -© 本文档使用 看云 构建 增加聚合模型扩展; 支持复合主键; 多表操作的支持完善; 模型的CURD操作支持仅获取SQL语句而不执行; 增加using/index/fetchSql/strict/token连贯操作方法; 模型类的setInc和setDec方法支持延迟写入; I函数增加变量修饰符和正则检测支持; 支持全局变量过滤和Action参数绑定的变量过滤; 修正可能的SQL注入漏洞; 支持全局路由定义; 增加插件控制器支持; 增加对全局和模块的模板路径的灵活设置; 日志目录分模块存放; 增加memcache Session驱动; 改进session函数的数组操作; 获取ThinkPHP 获取ThinkPHP的方式很多,官方网站(http://thinkphp.cn)是最好的下载和 文档获取来源。 官网提供了稳定版本的下载:http://thinkphp.cn/down/framework.html 官网下载版本提供了完整版和核心版两个版本,核心版本只保留了核心类库 和必须的文件,去掉了所有的扩展类库和驱动,支持标准模式和SAE模式。 如果你希望保持最新的更新,可以通过github获取当前最新的版本(完整 版)。 Git获取地址列表(你可以选择一个最快的地址): ThinkPHP3.2.3完全开发手册 - 5 -© 本文档使用 看云 构建 Github: https://github.com/liu21st/thinkphp Oschina: http://git.oschina.net/liu21st/thinkphp.git Code: https://code.csdn.net/topthink2011/ThinkPHP Coding: https://coding.net/u/liu21st/p/thinkphp/git 3.2版本也支持composer安装,确保你的电脑已经安装了composer,然后在 你的web根目录下面执行 composer create-project topthink/thinkphp your-project-name (注意:目前国内的速度很慢 建议直接下载官网版本注意:目前国内的速度很慢 建议直接下载官网版本) ThinkPHP无需任何安装,直接拷贝到你的电脑或者服务器的WEB运行目录下 面即可。 环境要求 框架本身没有什么特别模块要求,具体的应用系统运行环境要求视开发所涉及的 模块。ThinkPHP底层运行的内存消耗极低,而本身的文件大小也是轻量级的, 因此不会出现空间和内存占用的瓶颈。 PHP版本要求 PHP5.3以上版本(注意:PHP5.3dev版本和PHP6均不支持注意:PHP5.3dev版本和PHP6均不支持) 支持的服务器和数据库环境 支持Windows/Unix服务器环境 可运行于包括Apache、IIS和nginx在内的多种WEB服务器和模式 支持Mysql、MsSQL、PgSQL、Sqlite、Oracle、Ibase、Mongo等多种 数据库和连接 对于刚刚接触PHP或者ThinkPHP的新手,我们推荐使用集成开发环 ThinkPHP3.2.3完全开发手册 - 6 -© 本文档使用 看云 构建 境WAMPServer(wampserver是一个集成了Apache、PHP和MySQL的 开发套件,而且支持不同PHP版本、MySQL版本和Apache版本的切换)来 使用ThinkPHP进行本地开发和测试。 目录结构 下载框架后,解压缩到web目录下面,可以看到初始的目录结构如下: www WEB部署目录(或者子目录) ├─index.php 入口文件 ├─README.md README文件 ├─Application 应用目录 ├─Public 资源文件目录 └─ThinkPHP 框架目录 开发人员可以在这个基础之上灵活调整。其中, Application 和 Public 目录下 面都是空的。 README.md文件仅用于说明,实际部署的时候可以删除。 上面的目录结构和名称是可以改变的,这取决于你的入口文件和配置参数。 Application目录默认是空的,但是第一次访问入口文件会自动生成,参考后 面的入口文件部分。 其中框架目录ThinkPHP的结构如下: ThinkPHP3.2.3完全开发手册 - 7 -© 本文档使用 看云 构建 ├─ThinkPHP 框架系统目录(可以部署在非web目录下面) │ ├─Common 核心公共函数目录 │ ├─Conf 核心配置目录 │ ├─Lang 核心语言包目录 │ ├─Library 框架类库目录 │ │ ├─Think 核心Think类库包目录 │ │ ├─Behavior 行为类库目录 │ │ ├─Org Org类库包目录 │ │ ├─Vendor 第三方类库目录 │ │ ├─ ... 更多类库目录 │ ├─Mode 框架应用模式目录 │ ├─Tpl 系统模板目录 │ ├─LICENSE.txt 框架授权协议文件 │ ├─logo.png 框架LOGO文件 │ ├─README.txt 框架README文件 │ └─ThinkPHP.php 框架入口文件 上述应用的目录结构只是默认设置,事实上,在实际部署应用的时候,我们 建议除了应用入口文件和 Public 资源目录外,其他文件都放到非WEB目录 下面,具有更好的安全性。 入口文件 ThinkPHP采用单一入口模式单一入口模式进行项目部署和访问,无论完成什么功能,一个 应用都有一个统一(但不一定是唯一)的入口。 应该说,所有应用都是从入口文件开始的,并且不同应用的入口文件是类似的。 入口文件定义 入口文件主要完成: ThinkPHP3.2.3完全开发手册 - 8 -© 本文档使用 看云 构建 定义框架路径、项目路径(可选) 定义调试模式和应用模式(可选) 定义系统相关常量(可选) 载入框架入口文件(必须) 默认情况下,框架已经自带了一个应用入口文件(以及默认的目录结构),内容 如下: define('APP_PATH','./Application/'); require './ThinkPHP/ThinkPHP.php'; 如果你改变了项目目录(例如把 Application 更改为 Apps ),只需要在入口文 件更改APP_PAT HAPP_PAT H常量定义即可: define('APP_PATH','./Apps/'); require './ThinkPHP/ThinkPHP.php'; 注意:APP_PATH的定义支持相对路径和绝对路径,但必须以“/”结束 如果你调整了框架核心目录的位置或者目录名,只需要这样修改: define('APP_PATH','./Application/'); require './Think/ThinkPHP.php'; 也可以单独定义一个T HINK_PAT HT HINK_PAT H常量用于引入: define('APP_PATH','./Application/'); define('THINK_PATH',realpath('../Think').'/'); require THINK_PATH.'ThinkPHP.php'; 和APP_PATH一样THINK_PATH路径定义也必须以“/”结尾。 ThinkPHP3.2.3完全开发手册 - 9 -© 本文档使用 看云 构建 给THINK_PATH和APP_PATH定义绝对路径会提高系统的加载效率。 入口文件中的其他定义 一般不建议在入口文件中做过多的操作,但可以重新定义一些系统常量,入口文 件中支持定义(建议)的一些系统常量包括: 常量常量 描述描述 THINK_PATH 框架目录 APP_PATH 应用目录 RUNTIME_PATH 应用运行时目录(可写) APP_DEBUG 应用调试模式 (默认为false) STORAGE_TYPE 存储类型(默认为File) APP_MODE 应用模式(默认为common) 注意:所有路径常量都必须以“/”结尾 例如,我们可以在入口文件中重新定义相关目录并且开启调试模式: // 定义应用目录 define('APP_PATH','./Apps/'); // 定义运行时目录 define('RUNTIME_PATH','./Runtime/'); // 开启调试模式 define('APP_DEBUG',True); // 更名框架目录名称,并载入框架入口文件 require './Think/ThinkPHP.php'; 这样最终的应用目录结构如下: ThinkPHP3.2.3完全开发手册 - 10 -© 本文档使用 看云 构建 www WEB部署目录(或者子目录) ├─index.php 应用入口文件 ├─Apps 应用目录 ├─Public 资源文件目录 ├─Runtime 运行时目录 └─Think 框架目录 入口文件中还可以定义一些系统变量,用于相关的绑定操作(通常用于多个 入口的情况),这个会在后面涉及,暂且不提。 自动生成 自动创建目录 在第一次访问应用入口文件的时候,会显示如图所示的默认的欢迎页面,并自动 生成了一个默认的应用模块Home。 接下来再看原来空的 Application 目录下面,已经自动生成了公共模块 Common 、默认的 Home 模块和 Runtime 运行时目录的目录结构: ThinkPHP3.2.3完全开发手册 - 11 -© 本文档使用 看云 构建 Application ├─Common 应用公共模块 │ ├─Common 应用公共函数目录 │ └─Conf 应用公共配置文件目录 ├─Home 默认生成的Home模块 │ ├─Conf 模块配置文件目录 │ ├─Common 模块函数公共目录 │ ├─Controller 模块控制器目录 │ ├─Model 模块模型目录 │ └─View 模块视图文件目录 ├─Runtime 运行时目录 │ ├─Cache 模版缓存目录 │ ├─Data 数据目录 │ ├─Logs 日志目录 │ └─Temp 缓存目录 如果你不是Windows环境下面的话,需要对应用目录 Application 设置可写 权限才能自动生成。 如果不是调试模式的话,会在Runtime目录下面生成 common~runtime.php 文件(应用编译缓存文件)。 目录安全文件 在自动生成目录结构的同时,在各个目录下面我们还看到了index.html文件, 这是ThinkPHP自动生成的目录安全文件。 为了避免某些服务器开启了目录浏览权限后可以直接在浏览器输入URL地址查看 目录,系统默认开启了目录安全文件机制,会在自动生成目录的时候生成空白的 index.html 文件,当然安全文件的名称可以设置,例如你想给安全文件定义为 default.html 可以在入口文件中添加: ThinkPHP3.2.3完全开发手册 - 12 -© 本文档使用 看云 构建 define('DIR_SECURE_FILENAME', 'default.html'); define('APP_PATH','./Application/'); require './ThinkPHP/ThinkPHP.php'; 如果你的环境足够安全,不希望生成目录安全文件,可以在入口文件里面关闭目 录安全文件的生成,例如: define('BUILD_DIR_SECURE', false); 模块 下载后的框架自带了一个应用目录结构,并且带了一个默认的应用入口文件,方 便部署和测试,默认的应用目录是Application(实际部署过程中可以随意设 置),应用目录只有一个,因为大多数情况下,我们都可以通过多模块化以及多 入口的设计来解决应用的扩展需求。 模块设计 新版采用模块化的设计架构,下面是一个应用目录下面的模块目录结构,每个模 块可以方便的卸载和部署,并且支持公共模块。 Application 默认应用目录(可以设置) ├─Common 公共模块(不能直接访问) ├─Home 前台模块 ├─Admin 后台模块 ├─... 其他更多模块 ├─Runtime 默认运行时目录(可以设置) 每个模块是相对独立的,其目录结构如下: ThinkPHP3.2.3完全开发手册 - 13 -© 本文档使用 看云 构建 ├─Module 模块目录 │ ├─Conf 配置文件目录 │ ├─Common 公共函数目录 │ ├─Controller 控制器目录 │ ├─Model 模型目录 │ ├─Logic 逻辑目录(可选) │ ├─Service Service目录(可选) │ ... 更多分层目录可选 │ └─View 视图目录 由于采用多层的MVC机制,除了Conf和Common目录外,每个模块下面的 目录结构可以根据需要灵活设置和添加,所以并不拘泥于上面展现的目录 控制器 我们可以在自动生成的Application/Home/Controller目录下面找到一个 IndexController.class.php 文件,这就是默认的Index控制器文件。 控制器类的命名方式是:控制器名(驼峰法,首字母大写)+Controller控制器名(驼峰法,首字母大写)+Controller 控制器文件的命名方式是:类名+class.php(类文件后缀)类名+class.php(类文件后缀) 默认的欢迎页面其实就是访问的Home模块下面的Index控制器类的index操作 方法 我们修改默认的index操作方法如下: ThinkPHP3.2.3完全开发手册 - 14 -© 本文档使用 看云 构建 namespace Home\Controller; use Think\Controller; class IndexController extends Controller { public function index(){ echo 'hello,world!'; } } 再次运行应用入口文件,浏览器会显示: hello,world! 。 我们再来看下控制器类,IndexController控制器类的开头是命名空间定义: namespace Home\Controller; 这是系统的规范要求,表示当前类是Home模块下的控制器类,命名空间和实际 的控制器文件所在的路径是一致的,也就是说: Home\Controller\IndexController 类 对应的控制器文件位于应用目录下面 的 Home/Controller/IndexController.class.php ,如果你改变了当前的模 块名,那么这个控制器类的命名空间也需要随之修改。 注意:命名空间定义必须写在所有的PHP代码之前声明,而且之前不能有任 何输出,否则会出错 use Think\Controller; 表示引入 Think\Controller 类库便于直接使用。 所以, namespace Home\Controller; use Think\Controller; class IndexController extends Controller ThinkPHP3.2.3完全开发手册 - 15 -© 本文档使用 看云 构建 等同于使用: namespace Home\Controller; class IndexController extends \Think\Controller 开发规范 命名规范 使用ThinkPHP开发的过程中应该尽量遵循下列命名规范: 类文件都是以.class.php为后缀(这里是指的ThinkPHP内部使用的类库 文件,不代表外部加载的类库文件),使用驼峰法命名,并且首字母大 写,例如 DbMysql.class.php ; 类的命名空间地址和所在的路径地址一致,例如 Home\Controller\UserController 类所在的路径应该是 Application/Home/Controller/UserController.class.php ; 确保文件的命名和调用大小写一致,是由于在类Unix系统上面,对大小 写是敏感的(而ThinkPHP在调试模式下面,即使在Windows平台也会 严格检查大小写); 类名和文件名一致(包括上面说的大小写一致),例如 UserController 类的文件命名是 UserController.class.php , InfoModel类的文件名是 InfoModel.class.php , 并且不同的类库的类命名有一定的规范; 函数、配置文件等其他类库文件之外的一般是以 .php 为后缀(第三方引 入的不做要求); 函数的命名使用小写字母和下划线的方式,例如 get_client_ip ; 方法的命名使用驼峰法,并且首字母小写或者使用下划线“_”,例如 getUserName , _parseType ,通常下划线开头的方法属于私有方 法; ThinkPHP3.2.3完全开发手册 - 16 -© 本文档使用 看云 构建 属性的命名使用驼峰法,并且首字母小写或者使用下划线“_”,例如 tableName 、 _instance ,通常下划线开头的属性属于私有属性; 以双下划线“__”打头的函数或方法作为魔法方法,例如 __call 和 __autoload ; 常量以大写字母和下划线命名,例如 HAS_ONE 和 MANY_TO_MANY ; 配置参数以大写字母和下划线命名,例如 HTML_CACHE_ON ; 语言变量以大写字母和下划线命名,例如 MY_LANG ,以下划线打头的 语言变量通常用于系统语言变量,例如 _CLASS_NOT_EXIST_ ; 对变量的命名没有强制的规范,可以根据团队规范来进行; ThinkPHP的模板文件默认是以 .html 为后缀(可以通过配置修改); 数据表和字段采用小写加下划线方式命名,并注意字段名不要以下划线开 头,例如 think_user 表和 user_name 字段是正确写法,类似 _username 这样的数据表字段可能会被过滤。 特例:在ThinkPHP里面,有一个函数命名的特例,就是单字母大写函数,这类 函数通常是某些操作的快捷定义,或者有特殊的作用。例如:A、D、S、L 方法 等等,他们有着特殊的含义,后面会有所了解。 由于ThinkPHP默认全部使用UTF-8编码,所以请确保你的程序文件采用 UTF-8编码格式保存,并且去掉BOM信息头(去掉BOM头信息有很多方 式,不同的编辑器都有设置方法,也可以用工具进行统一检测和处理),否 则可能导致很多意想不到的问题。 开发建议 在使用ThinkPHP进行开发的过程中,我们给出如下建议,会让你的开发变得更 轻松: 遵循框架的命名规范和目录规范; 开发过程中尽量开启调试模式,及早发现问题; ThinkPHP3.2.3完全开发手册 - 17 -© 本文档使用 看云 构建 多看看日志文件,查找隐患问题; 养成使用I函数获取输入变量的好习惯; 更新或者环境改变后遇到问题首要问题是清空Runtime目录; 配置 ThinkPHP提供了灵活的全局配置功能,采用最有效率的PHP返回数组方式定 义,支持惯例配置、公共配置、模块配置、调试配置和动态配置。 对于有些简单的应用,你无需配置任何配置文件,而对于复杂的要求,你还可以 增加动态配置文件。 系统的配置参数是通过静态变量全局存取的,存取方式简单高效。 配置格式 PHP数组定义 ThinkPHP框架中默认所有配置文件的定义格式均采用返回PHP数组PHP数组的方式, 格式为: ThinkPHP3.2.3完全开发手册 - 18 -© 本文档使用 看云 构建 //项目配置文件 return array( 'DEFAULT_MODULE' => 'Index', //默认模块 'URL_MODEL' => '2', //URL模式 'SESSION_AUTO_START' => true, //是否开启session //更多配置参数 //... ); 配置参数不区分大小写(因为无论大小写定义都会转换成小写),所以下面的配 置等效: //项目配置文件 return array( 'default_module' => 'Index', //默认模块 'url_model' => '2', //URL模式 'session_auto_start' => true, //是否开启session //更多配置参数 //... ); 但是我们建议保持大写定义配置参数的规范。 还可以在配置文件中可以使用二维数组来配置更多的信息,例如: ThinkPHP3.2.3完全开发手册 - 19 -© 本文档使用 看云 构建 //项目配置文件 return array( 'DEFAULT_MODULE' => 'Index', //默认模块 'URL_MODEL' => '2', //URL模式 'SESSION_AUTO_START' => true, //是否开启session 'USER_CONFIG' => array( 'USER_AUTH' => true, 'USER_TYPE' => 2, ), //更多配置参数 //... ); 需要注意的是,二级参数配置区分大小写,也就说读取确保和定义一致。 其他配置格式支持 也可以采用 yaml/json/xml/ini 以及自定义格式的配置文件支持。 我们可以在应用入口文件应用入口文件中定义应用的配置文件的后缀,例如: define('CONF_EXT','.ini'); 定义后,应用的配置文件(包括模块的配置文件)后缀都统一采用.ini。 无论是什么格式的配置文件,最终都会解析成数组格式。 该配置不会影响框 架内部的配置文件加载。 ini格式ini格式配置示例: ThinkPHP3.2.3完全开发手册 - 20 -© 本文档使用 看云 构建 DEFAULT_MODULE=Index ;默认模块 URL_MODEL=2 ;URL模式 SESSION_AUTO_START=on ;是否开启session xml格式xml格式配置示例: Index 2 1 yaml格式yaml格式配置示例: default_module:Index #默认模块 url_model:2 #URL模式 session_auto_start:True #是否开启session json格式json格式配置示例: { "default_module":"Index", "url_model":2, "session_auto_start":True } 除了 yaml/json/xml/ini 格式之外,我们还可以自定义配置格式,定义如下: define('CONF_EXT','.test'); // 配置自定义配置格式(后缀) define('CONF_PARSE','parse_test'); // 对应的解析函数 ThinkPHP3.2.3完全开发手册 - 21 -© 本文档使用 看云 构建 假设我们的自定义配置格式是类似 var1=val1&var2=val2 之类的字符串,那 么parse_test定义如下: function parse_test($str){ parse_str($str,$config); return (array)$config; } CONF_PARSE定义的解析函数返回值必须是一个PHP索引数组。 配置加载 在ThinkPHP中,一般来说应用的配置文件是自动加载的,加载的顺序是: 惯例配置->应用配置->模式配置->调试配置->状态配置->模块配 置->扩展配置->动态配置 以上是配置文件的加载顺序,因为后面的配置会覆盖之前的同名配置(在没 有生效的前提下),所以配置的优先顺序从右到左。 下面说明下不同的配置文件的区别和位置: 惯例配置 惯例重于配置是系统遵循的一个重要思想,框架内置有一个惯例配置文件(位于 ThinkPHP/Conf/convention.php ),按照大多数的使用对常用参数进行了 默认配置。所以,对于应用的配置文件,往往只需要配置和惯例配置不同的或者 新增的配置参数,如果你完全采用默认配置,甚至可以不需要定义任何配置文 件。 ThinkPHP3.2.3完全开发手册 - 22 -© 本文档使用 看云 构建 建议仔细阅读下系统的惯例配置文件中的相关配置参数,了解下系统默认的 配置参数。 应用配置 应用配置文件也就是调用所有模块之前都会首先加载的公共配置文件(默认位于 Application/Common/Conf/config.php )。 如果更改了公共模块的名称的话,公共配置文件的位置也相应改变 模式配置(可选) 如果使用了普通应用模式之外的应用模式的话,还可以为应用模式(后面会有描 述)单独定义配置文件,文件命名规范是: Application/Common/Conf/config_应用模式名称.php (仅在运行该模式下 面才会加载)。 模式配置文件是可选的 调试配置(可选) 如果开启调试模式的话,则会自动加载框架的调试配置文件(位于 ThinkPHP/Conf/debug.php )和应用调试配置文件(位于 Application/Common/Conf/debug.php ) 状态配置(可选) 每个应用都可以在不同的情况下设置自己的状态(或者称之为应用场景),并且 加载不同的配置文件。 举个例子,你需要在公司和家里分别设置不同的数据库测试环境。那么可以这样 处理,在公司环境中,我们在入口文件中定义: ThinkPHP3.2.3完全开发手册 - 23 -© 本文档使用 看云 构建 define('APP_STATUS','office'); 那么就会自动加载该状态对应的配置文件(位于 Application/Common/Conf/office.php )。 如果我们回家后,我们修改定义为: define('APP_STATUS','home'); 那么就会自动加载该状态对应的配置文件(位于 Application/Common/Conf/home.php )。 状态配置文件是可选的 模块配置 每个模块会自动加载自己的配置文件(位于 Application/当前模块名/Conf/config.php )。 如果使用了普通模式之外的其他应用模式,你还可以为应用模式单独定义配置文 件,命名规范为: Application/当前模块名/Conf/config_应用模式名称.php (仅在运行该模式下面才会加载)。 模块还可以支持独立的状态配置文件,命名规范为: Application/当前模块名/Conf/应用状态.php 。 如果你的应用的配置文件比较大,想分成几个单独的配置文件或者需要加载额外 的配置文件的话,可以考虑采用扩展配置或者动态配置(参考后面的描述)。 读取配置 ThinkPHP3.2.3完全开发手册 - 24 -© 本文档使用 看云 构建 无论何种配置文件,定义了配置文件之后,都统一使用系统提供的C方法(可以 借助Config单词来帮助记忆)来读取已有的配置。 用法: C('参数名称') 例如,读取当前的URL模式配置参数: $model = C('URL_MODEL'); // 由于配置参数不区分大小写,因此下面的写法是等效的 // $model = C('url_model'); 但是建议使用大写方式的规范。 注意:配置参数名称中不能含有 “.” 和特殊字符,允许字母、数字和下划 线。 如果 url_model 尚未存在设置,则返回NULL。 支持在读取的时候设置默认值,例如: // 如果my_config尚未设置的话,则返回default_config字符串 C('my_config',null,'default_config'); C方法也可以用于读取二维配置: //获取用户配置中的用户类型设置 C('USER_CONFIG.USER_TYPE'); 因为配置参数是全局有效的,因此C方法可以在任何地方读取任何配置,即使某 个设置参数已经生效过期了。 ThinkPHP3.2.3完全开发手册 - 25 -© 本文档使用 看云 构建 动态配置 之前的方式都是通过预先定义配置文件的方式,而在具体的操作方法里面,我们 仍然可以对某些参数进行动态配置(或者增加新的配置),主要是指那些还没有 被使用的参数。 设置格式: C('参数名称','新的参数值') 例如,我们需要动态改变数据缓存的有效期的话,可以使用 // 动态改变缓存有效期 C('DATA_CACHE_TIME',60); 动态配置赋值仅对当前请求有效,不会对以后的请求造成影响。 动态改变配置参数的方法和读取配置的方法在使用上面非常接近,都是使用C方 法,只是参数的不同。 也可以支持二维数组的读取和设置,使用点语法进行操作,如下: // 获取已经设置的参数值 C('USER_CONFIG.USER_TYPE'); // 设置新的值 C('USER_CONFIG.USER_TYPE',1); 扩展配置 ThinkPHP3.2.3完全开发手册 - 26 -© 本文档使用 看云 构建 扩展配置可以支持自动加载额外的自定义配置文件,并且配置格式和项目配置一 样。 设置扩展配置的方式如下(多个文件用逗号分隔): // 加载扩展配置文件 'LOAD_EXT_CONFIG' => 'user,db', 假设扩展配置文件 user.php 和 db.php 分别用于用户配置和数据库配置,这样 做的好处是哪怕以后关闭调试模式,你修改db配置文件后依然会自动生效。 如果在应用公共设置文件中配置的话,那么会自动加载应用公共配置目录下面的 配置文件 Application/Common/Conf/user.php 和 Application/Common/Conf/db.php 。 如果在模块(假设是Home模块)的配置文件中配置的话,则会自动加载模块目 录下面的配置文件 Application/Home/Conf/user.php 和 Application/Home/Conf/db.php 。 默认情况下,扩展配置文件中的设置参数会并入项目配置文件中。也就是默认都 是一级配置参数,例如user.php中的配置参数如下: 2, //用户类型 'USER_AUTH_ID' => 10, //用户认证ID 'USER_AUTH_TYPE' => 2, //用户认证模式 ); 那么,最终获取用户参数的方式是: ThinkPHP3.2.3完全开发手册 - 27 -© 本文档使用 看云 构建 C('USER_AUTH_ID'); 如果配置文件改成: // 加载扩展配置文件 'LOAD_EXT_CONFIG' => array('USER'=>'user','DB'=>'db'), 则最终获取用户参数的方式改成: C('USER.USER_AUTH_ID'); 批量配置 C配置方法支持批量配置,例如: $config = array('WEB_SITE_TITLE'=>'ThinkPHP','WEB_SITE_DESCR IPTION'=>'开源PHP框架'); C($config); $config数组中的配置参数会合并到现有的全局配置中。 我们可以通过这种方式读取数据库中的配置参数,例如: // 读取数据库中的配置(假设有一个config表用于保存配置参数) $config = M('Config')->getField('name,value'); // config是一个关联数组 键值就是配置参数 值就是配置值 // 例如: array('config1'=>'val1','config2'=>'val2',...) C($config); // 合并配置参数到全局配置 ThinkPHP3.2.3完全开发手册 - 28 -© 本文档使用 看云 构建 合并之后,我们就可以和前面读取普通配置参数一样,读取数据库中的配置参数 了,当然也可以动态改变。 // 读取合并到全局配置中的数据库中的配置参数 C('CONFIG1'); // 动态改变配置参数(当前请求有效,不会自动保存到数据库) C('CONFIG2','VALUE_NEW'); 架构 模块化设计 一个完整的ThinkPHP应用基于模块/控制器/操作模块/控制器/操作设计,并且,如果有需要的 话,可以支持多入口文件和多级控制器。 ThinkPHP新版采用模块化的架构设计思想,对目录结构规范做了调整,可以支 持多模块应用的创建,让应用的扩展更加方便。 一个典型的URL访问规则是(我们以默认的PATHINFO模式为例说明,当然也 可以支持普通的URL模式): http://serverName/index.php(或者其他应用入口文件)/模块/控制器/ 操作/[参数名/参数值...] ThinkPHP的应用可以支持切换到命令行访问,如果切换到命令行模式下面的访 问规则是: ThinkPHP3.2.3完全开发手册 - 29 -© 本文档使用 看云 构建 >php.exe index.php(或其它应用入口文件) 模块/控制器/操作/[参数名/ 参数值...] 解释下其中的几个概念: 名名 称称 描述描述 应 用 基于同一个入口文件访问的项目我们称之为一个应用。 模 块 一个应用下面可以包含多个模块,每个模块在应用目录下面都是一个 独立的子目录。 控 制 器 每个模块可以包含多个控制器,一个控制器通常体现为一个控制器 类。 操 作 每个控制器类可以包含多个操作方法,也可能是绑定的某个操作类, 每个操作是URL访问的最小单元。 模块化设计的思想下面模块是最重要的部分,模块其实是一个包含配置文 件、函数文件和MVC文件(目录)的集合。 模块设计 新版采用模块化的设计架构,下面是一个应用目录下面的模块目录结构,每个模 块可以方便的卸载和部署,并且支持公共模块。 ThinkPHP3.2.3完全开发手册 - 30 -© 本文档使用 看云 构建 Application 默认应用目录(可以设置) ├─Common 公共模块(不能直接访问) ├─Home 前台模块 ├─Admin 后台模块 ├─... 其他更多模块 ├─Runtime 默认运行时目录(可以设置) 默认情况下,只要应用目录下面存在模块目录,该模块就可以访问,只有当 你希望禁止某些模块或者仅允许模块访问的时候才需要进行模块列表的相关 设置。 每个模块是相对独立的,其目录结构如下: ├─Module 模块目录 │ ├─Conf 配置文件目录 │ ├─Common 公共函数目录 │ ├─Controller 控制器目录 │ ├─Model 模型目录 │ ├─Logic 逻辑目录(可选) │ ├─Service Service目录(可选) │ ... 更多分层目录可选 │ └─View 视图目录 由于采用多层的MVC机制,除了Conf和Common目录外,每个模块下面的 目录结构可以根据需要灵活设置和添加,所以并不拘泥于上面展现的目录 公共模块 Common模块是一个特殊的模块,是应用的公共模块,访问所有的模块之前都 会首先加载公共模块下面的配置文件( Conf/config.php )和公共函数文件( Common/function.php )。但Common模块本身不能通过URL直接访Common模块本身不能通过URL直接访 问问,公共模块的其他文件则可以被其他模块继承或者调用。 ThinkPHP3.2.3完全开发手册 - 31 -© 本文档使用 看云 构建 公共模块的位置可以通过COM M ON_PAT HCOM M ON_PAT H常量改变,我们可以在入口文件 中重新定义COM M ON_PAT HCOM M ON_PAT H如下: define('COMMON_PATH','./Common/'); define('APP_PATH','./Application/'); require './ThinkPHP/ThinkPHP.php'; 其应用目录结构变成: www WEB部署目录(或者子目录) ├─index.php 入口文件 ├─README.md README文件 ├─Common 应用公共模块目录 ├─Application 应用模块目录 ├─Public 应用资源文件目录 └─ThinkPHP 框架目录 定义之后,Application目录下面就不再需要Common目录了。 自动生成模块目录 可以支持自动生成默认模块之外的模块目录以及批量生成控制器和模型类。 例如,如果我们需要生成一个Admin模块用于后台应用,在应用入口文件中定 义如下: // 绑定Admin模块到当前入口文件 define('BIND_MODULE','Admin'); define('APP_PATH','./Application/'); require './ThinkPHP/ThinkPHP.php'; 然后访问URL地址 ThinkPHP3.2.3完全开发手册 - 32 -© 本文档使用 看云 构建 http://serverName/index.php 就会生成Admin模块的目录,并生成一个默认的控制器类 Admin\Controller\IndexController 。 如果需要生成更多的控制器类,可以 定义 BUILD_CONTROLLER_LIST 常量,例如: // 绑定Admin模块到当前入口文件 define('BIND_MODULE','Admin'); define('BUILD_CONTROLLER_LIST','Index,User,Menu'); define('APP_PATH','./Application/'); require './ThinkPHP/ThinkPHP.php'; 访问后会自动生成三个指定的控制器类: Admin\Controller\IndexController Admin\Controller\UserController Admin\Controller\MenuController 注意:默认生成的控制器类都是继承 Think\Controller ,如果需要继承其他 的公共类需要另外调整。 如果在应用的公共配置文件中设置关闭了 APP_USE_NAMESPACE 的话,生成的控制器类则不会采用命名空间定 义。 同样,也可以定义 BUILD_MODEL_LIST 支持生成多个模型类: // 绑定Admin模块到当前入口文件 define('BIND_MODULE','Admin'); define('BUILD_MODEL_LIST','User,Menu'); define('APP_PATH','./Application/'); require './ThinkPHP/ThinkPHP.php'; ThinkPHP3.2.3完全开发手册 - 33 -© 本文档使用 看云 构建 访问会自动生成模型类: Admin\Model\UserModel Admin\Model\MenuModel 注意:默认生成的模型类都是继承 Think\Model ,如果需要继承公共的模 型类需要另外调整。 如果在应用的公共配置文件中设置关闭了 APP_USE_NAMESPACE 的话,生成的模型类则不会采用命名空间定义。 还可以自己手动调用 Think\Build 类的方法来生成控制器类和模型类,例如: // 生成Admin模块的Role控制器类 // 默认类库为Admin\Controller\RoleController // 如果已经存在则不会重新生成 \Think\Build::buildController('Admin','Role'); // 生成Admin模块的Role模型类 // 默认类库为Admin\Model\RoleModel // 如果已经存在则不会重新生成 \Think\Build::buildModel('Admin','Role'); 更多的方法可以参考Think\Build类库。 禁止访问模块 ThinkPHP对模块的访问是自动判断的,所以通常情况下无需配置模块列表即可 访问,但可以配置禁止访问的模块列表(用于被其他模块调用或者不开放访 问),默认配置中是禁止访问 Common 模块和 Runtime 模块(Runtime目录 是默认的运行时目录),我们可以增加其他的禁止访问模块列表: // 设置禁止访问的模块列表 'MODULE_DENY_LIST' => array('Common','Runtime','Api'), ThinkPHP3.2.3完全开发手册 - 34 -© 本文档使用 看云 构建 设置后,Api模块不能通过URL直接访问,事实上,可能我们只是在该模块下面 放置一些公共的接口文件,因此都是内部调用即可。 设置访问列表 如果你的应用下面模块比较少,还可以设置允许访问列表和默认模块,这样可以 简化默认模块的URL访问。 'MODULE_ALLOW_LIST' => array('Home','Admin','User'), 'DEFAULT_MODULE' => 'Home', 设置之后,除了Home、Admin和User模块之外的模块都不能被直接访问,并 且Home模块是默认访问模块(可以不出现在URL地址)。 单模块设计 如果你的应用够简单,那么也许仅仅用一个模块就可以完成,那么可以直接设 置: // 关闭多模块访问 'MULTI_MODULE' => false, 'DEFAULT_MODULE' => 'Home', 一旦关闭多模块访问后,就只能访问默认模块(这里设置的是Home)。 单模块设计后公共模块依然有效 多入口设计 可以给相同的应用及模块设置多个入口,不同的入口文件可以设置不同的应用模 式或者绑定模块。 例如,我们在 index.php 文件的同级目录新增一个 admin.php 入口文件,并绑 定Admin模块: ThinkPHP3.2.3完全开发手册 - 35 -© 本文档使用 看云 构建 // 绑定Home模块到当前入口文件 define('BIND_MODULE','Admin'); define('APP_PATH','./Application/'); require './ThinkPHP/ThinkPHP.php'; 如果你更改了系统默认的变量设置,则需要做对应的模块绑定的变量调整。 绑定模块后,原来的访问地址 http://serverName/index.php/Admin/Index/index 就变成 http://serverName/admin.php/Index/index 同样的方式,我们也可以在入口文件中绑定控制器,例如: define('BIND_MODULE', 'Home'); // 绑定Home模块到当前入口文件 define('BIND_CONTROLLER','Index'); // 绑定Index控制器到当前入口 文件 define('APP_PATH','./Application/'); require './ThinkPHP/ThinkPHP.php'; 绑定模块和控制器后,原来的访问地址: http://serverName/index.php/Home/Index/index 就变成: http://serverName/home.php/index ThinkPHP3.2.3完全开发手册 - 36 -© 本文档使用 看云 构建 不同的入口文件还可以用于绑定不同的应用模式,参考应用模式部分。 URL模式 入口文件是应用的单一入口,对应用的所有请求都定向到应用入口文件,系统会 从URL参数中解析当前请求的模块、控制器和操作: http://serverName/index.php/模块/控制器/操作 这是3.2版本的标准URL格式。 可以通过设置模块绑定或者域名部署等方式简化URL地址中的模块及控制器 名称。 URL大小写 ThinkPHP框架的URL是区分大小写(主要是针对模块、控制器和操作名,不包 括应用参数)的,这一点非常关键,因为ThinkPHP的命名规范是采用驼峰法 (首字母大写)的规则,而URL中的模块和控制器都是对应的文件,因此在 Linux环境下面必然存在区分大小写的问题。 框架内置了一个配置参数用于解决URL大小写的问题,如下: 'URL_CASE_INSENSITIVE' => true, 当 URL_CASE_INSENSITIVE 设置为true的时候表示URL地址不区分大小写, 这个也是框架在部署模式下面的默认设置。 当开启调试模式的情况下,这个参数是false,因此你会发现在调试模式下面 URL区分大小写的情况。 ThinkPHP3.2.3完全开发手册 - 37 -© 本文档使用 看云 构建 URL模式 如果我们直接访问入口文件的话,由于URL中没有模块、控制器和操作,因此系 统会访问默认模块(Home)下面的默认控制器(Index)的默认操作 (index),因此下面的访问是等效的: http://serverName/index.php http://serverName/index.php/Home/Index/index 这种URL模式就是系统默认的PATHINFO模式,不同的URL模式获取模块和操 作的方法不同,ThinkPHP支持的URL模式有四种:普通模式、普通模式、 PAT HINFO、REWRIT E和兼容模式PAT HINFO、REWRIT E和兼容模式,可以设置URL_M ODELURL_M ODEL参数改变 URL模式。 URL模式URL模式 URL_M ODEL设置URL_M ODEL设置 普通模式 0 PATHINFO模式 1 REWRITE模式 2 兼容模式 3 如果你整个应用下面的模块都是采用统一的URL模式,就可以在应用配置文 件中设置URL模式,如果不同的模块需要设置不同的URL模式,则可以在模 块配置文件中设置。 普通模式 普通模式普通模式也就是传统的GET传参方式来指定当前访问的模块和操作,例如: http://localhost/?m=home&c=user&a=login&var=value m参数表示模块,c参数表示控制器,a参数表示操作(当然这些参数都是可以配 ThinkPHP3.2.3完全开发手册 - 38 -© 本文档使用 看云 构建 置的),后面的表示其他GET参数。 如果默认的变量设置和你的应用变量有冲突的话,你需要重新设置系统配置,例 如改成下面的: 'VAR_MODULE' => 'module', // 默认模块获取变量 'VAR_CONTROLLER' => 'controller', // 默认控制器获取变量 'VAR_ACTION' => 'action', // 默认操作获取变量 上面的访问地址则变成: http://localhost/?module=home&controller=user&action=login&var =value 注意,VAR_MODULE只能在应用配置文件中设置,其他参数可以则也可以 在模块配置中设置 PATHINFO模式 PAT HINFO模式PAT HINFO模式是系统的默认URL模式,提供了最好的SEO支持,系统内部 已经做了环境的兼容处理,所以能够支持大多数的主机环境。对应上面的URL模 式,PATHINFO模式下面的URL访问地址是: http://localhost/index.php/home/user/login/var/value/ PATHINFO地址的前三个参数分别表示模块/控制器/操作。 不过,PATHINFO模式下面,依然可以采用普通URL模式的参数方式,例 如: http://localhost/index.php/home/user/login?var=value 依然是 有效的 PATHINFO模式下面,URL是可定制的,例如,通过下面的配置: ThinkPHP3.2.3完全开发手册 - 39 -© 本文档使用 看云 构建 // 更改PATHINFO参数分隔符 'URL_PATHINFO_DEPR'=>'-', 我们还可以支持下面的URL访问: http://localhost/index.php/home-user-login-var-value REWRITE模式 REWRIT E模式REWRIT E模式是在PATHINFO模式的基础上添加了重写规则的支持,可以去 掉URL地址里面的入口文件index.php,但是需要额外配置WEB服务器的重写规 则。 如果是Apache则需要在入口文件的同级添加.htaccess文件,内容如下: RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] 接下来,就可以用下面的URL地址访问了: http://localhost/home/user/login/var/value 更多环境的URL重写支持参考部署部分的URL重写。 兼容模式 兼容模式兼容模式是用于不支持PATHINFO的特殊环境,URL地址是: http://localhost/?s=/home/user/login/var/value 可以更改兼容模式变量的名称定义,例如: ThinkPHP3.2.3完全开发手册 - 40 -© 本文档使用 看云 构建 'VAR_PATHINFO' => 'path' PATHINFO参数分隔符对兼容模式依然有效,例如: // 更改PATHINFO参数分隔符 'URL_PATHINFO_DEPR'=>'-', 使用以上配置的话,URL访问地址可以变成: http://localhost/?path=/home-user-login-var-value 兼容模式配合Web服务器重写规则的定义,可以达到和REWRITE模式一样的 URL效果。 例如,我们在Apache下面的话,.htaccess文件改成如下内容: RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php?s=/$1 [QSA,PT,L] 就可以和REWRITE模式一样访问下面的URL地址访问了: http://localhost/home/user/login/var/value 多层MVC ThinkPHP基于MVC(Model-View-Controller,模型-视图-控制器)模式, 并且均支持多层(multi-Layer)设计。 ThinkPHP3.2.3完全开发手册 - 41 -© 本文档使用 看云 构建 模型(Model)层 默认的模型层由Model类构成,但是随着项目的增大和业务体系的复杂化,单 一的模型层很难解决要求,ThinkPHP支持多层Model,设计思路很简单,不 同的模型层仍然都继承自系统的Model类,但是在目录结构和命名规范上做了 区分。 例如在某个项目设计中需要区分数据层、逻辑层、服务层等不同的模型层,我们 可以在模块目录下面创建 Model 、 Logic 和 Service 目录,把对用户表的所有 模型操作分成三层: 1. 数据层:Model/UserModel 用于定义数据相关的自动验证和自动完成和 数据存取接口 2. 逻辑层:Logic/UserLogic 用于定义用户相关的业务逻辑 3. 服务层:Service/UserService 用于定义用户相关的服务接口等 而这三个模型操作类统一都继承Model类即可,例如: 数据层: Home/Model/UserModel.class.php namespace Home\Model; use Think\Model; class UserModel extends Model{ } 逻辑层: Home/Logic/UserLogic.class.php namespace Home\Logic; use Think\Model; class UserLogic extends Model{ } 服务层: Home/Service/UserService.class.php ThinkPHP3.2.3完全开发手册 - 42 -© 本文档使用 看云 构建 namespace Home\Service; use Think\Model; class UserService extends Model{ } 这样区分不同的模型层之后对用户数据的操作就非常清晰,在调用的时候,我们 也可以用内置的D方法很方便的调用: D('User') //实例化UserModel D('User','Logic') //实例化UserLogic D('User','Service') //实例化UserService 默认的模型层是Model,我们也可以更改设置,例如: 'DEFAULT_M_LAYER' => 'Logic', // 更改默认的模型层名称为Logic 更改之后,实例化的时候需要改成: D('User') //实例化UserLogic D('User','Model') //实例化UserModel D('User','Service') //实例化UserService 对模型层的分层划分是很灵活的,开发人员可以根据项目的需要自由定义和 增加模型分层,你也完全可以只使用Model层。 视图(View)层 视图层由模板和模板引擎组成,在模板中可以直接使用PHP代码,模板引擎的 设计会在后面讲述,通过驱动也可以支持其他第三方的模板引擎。视图的多层可 以简单的通过目录(也就是模板主题)区分,例如: ThinkPHP3.2.3完全开发手册 - 43 -© 本文档使用 看云 构建 View/default/User/add.html View/blue/User/add.html 复杂一点的多层视图还可以更进一步,采用不同的视图目录来完成,例如: view 普通视图层目录 mobile 手机端访问视图层目录 这样做的好处是每个不同的视图层都可以支持不同的模板主题功能。 默认的视图层是View目录,我们可以调整设置如下: 'DEFAULT_V_LAYER' => 'Mobile', // 默认的视图层名称更改为Mo bile 非默认视图层目录的模板获取需要使用T函数,后面会讲到。 控制器(Controller)层 ThinkPHP的控制器层由核心控制器核心控制器和业务控制器业务控制器组成,核心控制器由系统内 部的App类完成,负责应用(包括模块、控制器和操作)的调度控制,包括 HTTP请求拦截和转发、加载配置等。业务控制器则由用户定义的控制器类完 成。多层业务控制器的实现原理和模型的分层类似,例如业务控制器和事件控制 器: Controller/UserController //用于用户的业务逻辑控制和调度 Event/UserEvent //用于用户的事件响应操作 访问控制器 Home/Controller/UserController.class.php 定义如下: ThinkPHP3.2.3完全开发手册 - 44 -© 本文档使用 看云 构建 namespace Home\Controller; use Think\Controller; class UserController extends Controller{ } 事件控制器 Home/Event/UserEvent.class.php 定义如下: namespace Home\Event; use Think\Controller; class UserEvent extends Controller{ } UserController负责外部交互响应,通过URL请求响应,例如 http://serverName/User/index ,而 UserEvent 负责内部的事件响应,并且 只能在内部调用: A('User','Event'); 默认的访问控制器层是Controller,我们可以调整设置如下: 'DEFAULT_C_LAYER' => 'Event', // 默认的控制器层名称改为Event 所以是和外部隔离的。 多层控制器的划分也不是强制的,可以根据应用的需要自由分层。控制器分层里 面可以根据需要调用分层模型,也可以调用不同的分层视图(主题)。 在MVC三层中,ThinkPHP并不依赖M或者V,甚至可以只有C或者只有V, 这个在ThinkPHP的设计里面是一个很重要的用户体验设计,用户只需要定 义视图,在没有C的情况下也能自动识别。 ThinkPHP3.2.3完全开发手册 - 45 -© 本文档使用 看云 构建 CBD模式 ThinkPHP引入了全新的CBD(核心Core+行为Behavior+驱动CBD(核心Core+行为Behavior+驱动 Driver)Driver)架构模式,从底层开始,框架就采用核心+行为+驱动的架构体系,核 心保留了最关键的部分,并在重要位置设置了标签用以标记,其他功能都采用行 为扩展和驱动的方式组合,开发人员可以根据自己的需要,对某个标签位置进行 行为扩展或者替换,就可以方便的定制框架底层,也可以在应用层添加自己的标 签位置和添加应用行为。而标签位置类似于AOP概念中的“切面”,行为都是 围绕这个“切面”来进行编程。 Core(核心) ThinkPHP的核心部分包括核心函数库、惯例配置、核心类库(包括基础类和内 置驱动及核心行为),这些是ThinkPHP必不可少的部分。 ThinkPHP3.2.3完全开发手册 - 46 -© 本文档使用 看云 构建 ThinkPHP/Common/functions.php // 核心函数库 ThinkPHP/Conf/convention.php // 惯例配置文件 ThinkPHP/Conf/debug.php // 惯例调试配置文件 ThinkPHP/Mode/common.php // 普通模式定义文件 ThinkPHP/Library/Think // 核心类库包 ThinkPHP/Library/Behavior // 系统行为类库 ThinkPHP/Library/Think/App.class.php // 核心应用类 ThinkPHP/Library/Think/Cache.class.php // 核心缓存类 ThinkPHP/Library/Think/Controller.class.php // 基础控制器类 ThinkPHP/Library/Think/Db.class.php // 数据库操作类 ThinkPHP/Library/Think/Dispatcher.class.php // URL解析调度类 ThinkPHP/Library/Think/Exception.class.php // 系统基础异常类 ThinkPHP/Library/Think/Hook.class.php // 系统钩子类 ThinkPHP/Library/Think/Log.class.php // 系统日志记录类 ThinkPHP/Library/Think/Model.class.php // 系统基础模型类 ThinkPHP/Library/Think/Route.class.php // 系统路由类 ThinkPHP/Library/Think/Storage.class.php // 系统存储类 ThinkPHP/Library/Think/Template.class.php // 内置模板引擎类 ThinkPHP/Library/Think/Think.class.php // 系统引导类 ThinkPHP/Library/Think/View.class.php // 系统视图类 Behavior目录下面是系统内置的一些行为类库,内置驱动则分布在各个不同的 驱动目录下面(参考下面的驱动部分)。 Driver(驱动) 3.2版本在架构设计上更加强化了驱动的设计,替代了之前的引擎和模式扩展, 并且改进了行为的设计,使得框架整体更加灵活,并且由于在需要写入数据的功 能类库中都采用了驱动化的设计思想,所以使得新的框架能够轻松满足分布式部 署的需求,对云平台的支持可以更简单的实现了。因此,在新版的扩展里面,已 经取消了引擎扩展和模式扩展,改成配置不同的应用模式即可。 驱动包括 ThinkPHP3.2.3完全开发手册 - 47 -© 本文档使用 看云 构建 ThinkPHP/Library/Think/Cache/Driver // 缓存驱动类库 ThinkPHP/Library/Think/Db/Driver // 数据库驱动类库 ThinkPHP/Library/Think/Log/Driver // 日志记录驱动类库 ThinkPHP/Library/Think/Session/Driver // Session驱动类库 ThinkPHP/Library/Think/Storage/Driver // 存储驱动类库 ThinkPHP/Library/Think/Template/Driver // 第三方模板引擎驱动类库 ThinkPHP/Library/Think/Template/TagLib // 内置模板引擎标签库扩展 类库 Behavior(行为) 行为(Behavior)是ThinkPHP扩展机制中比较关键的一项扩展,行为既可以 独立调用,也可以绑定到某个标签(位)中进行侦听。这里的行为指的是一个比 较抽象的概念,你可以想象成在应用执行过程中的一个动作或者处理,在框架的 执行流程中,各个位置都可以有行为产生,例如路由检测是一个行为,静态缓存 是一个行为,用户权限检测也是行为,大到业务逻辑,小到浏览器检测、多语言 检测等等都可以当做是一个行为,甚至说你希望给你的网站用户的第一次访问弹 出Hello,world!这些都可以看成是一种行为,行为的存在让你无需改动框架 和应用,而在外围通过扩展或者配置来改变或者增加一些功能。 而不同的行为之间也具有位置共同性,比如,有些行为的作用位置都是在应用执 行前,有些行为都是在模板输出之后,我们把这些行为发生作用的位置称之 为标签(位)标签(位),也可以称之为钩子,当应用程序运行到这个标签的时候,就会 被拦截下来,统一执行相关的行为,类似于AOP编程中的“切面”的概念,给 某一个标签绑定相关行为就成了一种类AOP编程的思想。 系统标签位 系统核心提供的标签位置包括(按照执行顺序排列): app_init 应用初始化标签位 module_check 模块检测标签位(3.2.1版本新增3.2.1版本新增) path_info PATH_INFO检测标签位 ThinkPHP3.2.3完全开发手册 - 48 -© 本文档使用 看云 构建 app_begin 应用开始标签位 action_name 操作方法名标签位 action_begin 控制器开始标签位 view_begin 视图输出开始标签位 view_template 视图模板解析标签位 view_parse 视图解析标签位 template_filter 模板解析过滤标签位 view_filter 视图输出过滤标签位 view_end 视图输出结束标签位 action_end 控制器结束标签位 app_end 应用结束标签位 在每个标签位置,可以配置多个行为,行为的执行顺序按照定义的顺序依次执 行。除非前面的行为里面中断执行了(某些行为可能需要中断执行,例如检测机 器人或者非法执行行为),否则会继续下一个行为的执行。 除了这些系统内置标签之外,开发人员还可以在应用中添加自己的应用标签,在 任何需要拦截的位置添加如下代码即可: // 添加my_tag 标签侦听 \Think\Hook::listen('my_tag'); 方法第一个参数是要侦听的标签位,除此之外还可以传入并且只接受一个参数, 如果需要传入多个参数,请使用数组。 // 添加my_tag 标签侦听 \Think\Hook::listen('my_tag',$params); 该参数为引用传值,所以只能传入变量,因此下面的传值是错误的: ThinkPHP3.2.3完全开发手册 - 49 -© 本文档使用 看云 构建 // 添加my_tag 标签侦听 \Think\Hook::listen('my_tag','param'); 核心行为 系统的很多核心功能也是采用行为扩展组装的,对于满足项目日益纷繁复杂的需 求和定制底层框架提供了更多的方便和可能性。 核心行为位于 ThinkPHP/Behavior/ 目录下面,框架核心内置的行为包括如 下: 行为名称行为名称 说明说明 对应标签位对应标签位 置置 BuildLite 生成Lite文件(3.2.1版本新增) app_init ParseTemplate 模板文件解析,并支持第三方模板引 擎驱动 view_parse ShowPageTrace 页面Trace功能行为,完成页面Trace 功能 view_end ShowRuntime 运行时间显示行为,完成运行时间显 示 view_filter TokenBuild 令牌生成行为,完成表单令牌的自动 生成 view_filter ReadHtmlCache 读取静态缓存行为 app_init WriteHtmlCache 生成静态缓存行为 view_filter 行为定义 自定义的扩展行为可以放在核心或者应用目录,只要遵循命名空间的定义规则即 可。 ThinkPHP3.2.3完全开发手册 - 50 -© 本文档使用 看云 构建 行为类的命名采用:#### 行为名称(驼峰法,首字母大写)+Behavior 行为类的定义方式如下: namespace Home\Behavior; class TestBehavior { // 行为扩展的执行入口必须是run public function run(&$params){ if(C('TEST_PARAM')) { echo 'RUNTEST BEHAVIOR '.$params; } } } 行为类必须定义执行入口方法 run ,由于行为的调用机制影响,run方法不需要任 何返回值,所有返回都通过引用返回。 run方法的参数只允许一个,但可以传入数组。 行为绑定 行为定义完成后,就需要绑定到某个标签位置才能生效,否则是不会执行的。 我们需要在应用的行为定义文件 tags.php 文件中进行行为和标签的位置定义, 格式如下: return array( '标签名称1'=>array('行为名1','行为名2',...), '标签名称2'=>array('行为名1','行为名2',...), ); 标签名称包括我们前面列出的系统标签和应用中自己定义的标签名称,比如你需 要在app_init标签位置定义一个 CheckLangBehavior 行为类的话,可以使用: ThinkPHP3.2.3完全开发手册 - 51 -© 本文档使用 看云 构建 return array( 'app_init'=>array('Home\Behavior\CheckLangBehavior'), ); 可以给一个标签位定义多个行为,行为的执行顺序就是定义的先后顺序,例如: return array( 'app_init'=>array( 'Home\Behavior\CheckLangBehavior', 'Home\Behavior\CronRunBehavior' ), ); 默认情况下tags.php中定义的行为会并入系统行为一起执行,也就是说如果系 统的行为定义中app_init标签中已经定义了其他行为,则会首先执行系统行为扩 展中定义的行为,然后再执行项目行为中定义的行为。例如: 系统行为定义文 件中定义了: 'app_begin' => array( 'Behavior\ReadHtmlCacheBehavior', // 读取静态缓存 ), 而应用行为定义文件有定义: 'app_begin' => array( 'Home\Behavior\CheckModuleBehavior', 'Home\Behavior\CheckLangBehavior', ), 则最终执行到app_begin标签(位)的时候,会依次执行: ThinkPHP3.2.3完全开发手册 - 52 -© 本文档使用 看云 构建 Library\Behavior\ReadHtmlCacheBehavior Home\Behavior\CheckModuleBehavior Home\Behavior\CheckLangBehavior 三个行为(除非中间某个行为有中止执行的操作)。 如果希望应用的行为配置文件中的定义覆盖系统的行为定义,可以改为为如下方 式: 'app_begin' => array( 'Home\Behavior\CheckModuleBehavior', 'Home\Behavior\CheckLangBehavior', '_overlay' => true, ), 则最终执行到app_begin标签(位)的时候,会依次执行下面两个行为: Home\Behavior\CheckModuleBehavior Home\Behavior\CheckLangBehavior 应用行为的定义没有限制,你可以把一个行为绑定到多个标签位置执行,例如: return array( 'app_begin'=>array('Home\Behavior\TestBehavior'), // 在app_be gin 标签位添加Test行为 'app_end'=>array('Home\Behavior\TestBehavior'), // 在app_end 标签位添加Test行为 ); 单独执行 行为的调用不一定要放到标签才能调用,如果需要的话,我们可以在控制器中或 ThinkPHP3.2.3完全开发手册 - 53 -© 本文档使用 看云 构建 者其他地方直接调用行为。例如,我们可以把用户权限检测封装成一个行为类, 例如: namespace Home\Behavior; use Think\Behavior; class AuthCheckBehavior extends Behavior { // 行为扩展的执行入口必须是run public function run(&$return){ if(C('USER_AUTH_ON')) { // 进行权限认证逻辑 如果认证通过 $return = true; // 否则用halt输出错误信息 } } } 定义了AuthCheck行为后,我们可以在控制器的_initialize方法中直接用下面的 方式调用: B('Home\Behavior\AuthCheck'); 命名空间 3.2版本全面采用命名空间方式定义和加载类库文件,有效的解决多个模块之间 的冲突问题,并且实现了更加高效的类库自动加载机制。 命名空间的概念必须了解,否则会成为学习3.2版本开发的重大障碍。 如果不清楚什么是命名空间,可以参考PHP手册:PHP命名空间 由于新版完全采用了命名空间的特性,因此只需要给类库正确定义所在的命名空 ThinkPHP3.2.3完全开发手册 - 54 -© 本文档使用 看云 构建 间,而命名空间的路径与类库文件的目录一致,那么就可以实现类的自动加载。 例如, Org\Util\File 类的定义为: namespace Org\Util; class File { } 其所在的路径是 ThinkPHP/Library/Org/Util/File.class.php ,因此,如果我 们实例化该类的话: $class = new \Org\Util\File(); 系统会自动加载 ThinkPHP/Library/Org/Util/File.class.php 文件。 根命名空间 根命名空间是一个关键的概念,以上面的 Org\Util\File 类为例, Org 就是一个 根命名空间,其对应的初始命名空间目录就是系统的类库目录( ThinkPHP/Library ),Library目录下面的子目录会自动识别为根命名空间, 这些命名空间无需注册即可使用。 例如,我们在Library目录下面新增一个My根命名空间目录,然后定义一个 Test类如下: namespace My; class Test { public function sayHello(){ echo 'hello'; } } Test类保存在 ThinkPHP/Library/My/Test.class.php ,我们就可以直接实 ThinkPHP3.2.3完全开发手册 - 55 -© 本文档使用 看云 构建 例化和调用: $Test = new \My\Test(); $Test->sayHello(); 模块中的类库命名空间的根都是以模块名命名,例如: namespace Home\Model; class UserModel extends \Think\Model { } 其类文件位于 Application/Home/Model/UserModel.class.php 。 namespace Admin\Event; class UserEvent { } 其类文件位于 Application/Admin/Event/UserEvent.class.php 。 特别注意:如果你需要在3.2版本中实例化PHP内置的类库或者第三方的没有 使用命名空间定义的类,需要采用下面的方式: // 必须从根命名空间调用系统内置的类库或者第三方没有使用命名空间的类 库 $class = new \stdClass(); $sxml = new \SimpleXmlElement($xmlstr); 自动加载 ThinkPHP3.2.3完全开发手册 - 56 -© 本文档使用 看云 构建 在3.2中,基本上无需手动加载类库文件,你可以很方便的完成自动加载。 命名空间自动加载 系统可以通过类的命名空间自动定位到类库文件,例如: 我们定义了一个类 \Org\Util\Auth 类: namespace Org\Util; class Auth { } 保存到 ThinkPHP/Library/Org/Util/Auth.class.php 。 接下来,我们就可以直接实例化了。 new \Org\Util\Auth(); 在实例化 \Org\Util\Auth 类的时候,系统会自动加载 ThinkPHP/Library/Org/Util/Auth.class.php 文件。 框架的Library目录下面的命名空间都可以自动识别和定位,例如: ├─Library 框架类库目录 │ ├─Think 核心Think类库包目录 │ ├─Org Org类库包目录 │ ├─ ... 更多类库目录 Library目录下面的子目录都是一个根命名空间,也就是说以Think、Org为根命 名空间的类都可以自动加载: ThinkPHP3.2.3完全开发手册 - 57 -© 本文档使用 看云 构建 new \Think\Cache\Driver\File(); new \Org\Util\Auth(); new \Org\Io\File(); 都可以自动加载对应的类库文件。 你可以在Library目录下面任意增加新的目录,就会自动注册成为一个新的根 命名空间。 注册新的命名空间 除了Library目录下面的命名空间之外,我们还可以注册其他的根命名空间,例 如: 'AUTOLOAD_NAMESPACE' => array( 'My' => THINK_PATH.'My', 'One' => THINK_PATH.'One', ) 配置了上面的 AUTOLOAD_NAMESPACE 后,如果我们实例化下面的类库 new \My\Net\IpLocation(); new \One\Util\Log(); 会自动加载对应的类库文件 ThinkPHP/My/Net/IpLocation.class.php ThinkPHP/One/Util/Log.class.php 如果命名空间不在Library目录下面,并且没有定义对应的 AUTOLOAD_NAMESPACE 参数的话,则会当作模块的命名空间进行自动加 ThinkPHP3.2.3完全开发手册 - 58 -© 本文档使用 看云 构建 载,例如: new \Home\Model\UserModel(); new \Home\Event\UserEvent(); 由于ThinkPHP/Library目录下面不存在Home目录,也没在 AUTOLOAD_NAMESPACE 参数定义Home命名空间,所以就把Home当成 模块命名空间来识别,所以会自动加载: Application/Home/Model/UserModel.class.php Application/Home/Event/UserEvent.class.php 注意:命名空间的大小写需要和目录名的大小写对应,否则可能会自动加载 失败。 类库映射 遵循我们上面的命名空间定义规范的话,基本上可以完成类库的自动加载了,但 是如果定义了较多的命名空间的话,效率会有所下降,所以,我们可以给常用的 类库定义类库映射。 命名类库映射相当于给类文件定义了一个别名,效率会比命名空间定位更高效, 例如: Think\Think::addMap('Think\Log',THINK_PATH.'Think\Log.php'); Think\Think::addMap('Org\Util\Array',THINK_PATH.'Org\Util\Array. php'); 注意:添加类库映射的时候不需要写类库开头的"\" 也可以利用addMap方法批量导入类库映射定义,例如: ThinkPHP3.2.3完全开发手册 - 59 -© 本文档使用 看云 构建 $map = array('Think\Log'=>THINK_PATH.'Think\Log.php','Org\Util \Array'=>THINK_PATH.'Org\Util\Array.php'); Think\Think::addMap($map); 当然,比较方便的方式是我们可以在模块配置目录下面创建alias.php文件用于 定义类库映射,该文件会自动加载,定义方式如下: return array( 'Think\Log' => THINK_PATH.'Think\Log.php', 'Org\Util\Array' => THINK_PATH.'Org\Util\Array.php' ); 自动加载的优先级 在实际的应用类库加载过程中,往往会涉及到自动加载的优先级问题,以 Test\MyClass 类为例,自动加载的优先顺序如下: 1. 判断是否有注册了Test\MyClass类库映射,如果有则自动加载类库映射定 义的文件; 2. 判断是否存在Library/Test目录,有则以该目录为初始目录加载; 3. 判断是否有注册Test根命名空间,有则以注册的目录为初始目录加载; 4. 如果以上都不成立,则以Test为模块目录进行初始目录加载; 然后以上面获取到的初始目录加载命名空间对应路径的文件; 手动加载第三方类库 如果要加载第三方类库,包括不符合命名规范和后缀的类库,以及没有使用不符合命名规范和后缀的类库,以及没有使用 命名空间或者命名空间和路径不一致的类库命名空间或者命名空间和路径不一致的类库,或者你就是想手动加载类库文 件,我们都可以通过手动导入的方式加载。 我们可以使用import方法导入任何类库,用法如下: ThinkPHP3.2.3完全开发手册 - 60 -© 本文档使用 看云 构建 // 导入Org类库包 Library/Org/Util/Date.class.php类库 import("Org.Util.Date"); // 导入Home模块下面的 Application/Home/Util/UserUtil.class.php类 库 import("Home.Util.UserUtil"); // 导入当前模块下面的类库 import("@.Util.Array"); // 导入Vendor类库包 Library/Vendor/Zend/Server.class.php import('Vendor.Zend.Server'); 对于import方法,系统会自动识别导入类库文件的位置,ThinkPHP可以自动 识别的类库包包括Think、Org、Com、Behavior和Vendor包,以及Library 目录下面的子目录,如果你在Library目录下面创建了一个Test子目录,并且创 建了一个UserTest.class.php类库,那么可以这样导入: import('Test.UserTest'); 其他的就认为是应用类库导入。 注意,如果你的类库没有使用命名空间定义的话,实例化的时候需要加上根命名 空间,例如: import('Test.UserTest'); $test = new \UserTest(); 按照系统的规则,import方法是无法导入具有点号的类库文件的,因为点号会 直接转化成斜线,例如我们定义了一个名称为User.Info.class.php 的文件的 话,采用: import("Org.User.Info"); ThinkPHP3.2.3完全开发手册 - 61 -© 本文档使用 看云 构建 方式加载的话就会出现错误,导致加载的文件不是Org/User.Info.class.php 文 件,而是Org/User/Info.class.php 文件,这种情况下,我们可以使用: import("Org.User#Info"); 来导入。 大多数情况下,import方法都能够自动识别导入类库文件的位置,如果是特殊 情况的导入,需要指定import方法的第二个参数作为起始导入路径。例如,要 导入当前文件所在目录下面的 RBAC/AccessDecisionManager.class.php 文 件,可以使用: import("RBAC.AccessDecisionManager",dirname(__FILE__)); 如果你要导入的类库文件名的后缀不是class.php而是php,那么可以使用 import方法的第三个参数指定后缀: import("RBAC.AccessDecisionManager",dirname(__FILE__),".php"); 注意:在Unix或者Linux主机下面是区别大小写的,所以在使用import方法 的时候要注意目录名和类库名称的大小写,否则会导入失败。 如果你的第三方类库都放在Vendor目录下面,并且都以.php为类文件后缀,也 没用采用命名空间的话,那么可以使用系统内置的Vendor函数简化导入。 例 如,我们把 Zend 的 Filter\Dir.php 放到 Vendor 目录下面,这个时候 Dir 文 件的路径就是 Vendor\Zend\Filter\Dir.php,我们使用vendor 方法导入只需 要使用: Vendor('Zend.Filter.Dir'); ThinkPHP3.2.3完全开发手册 - 62 -© 本文档使用 看云 构建 就可以导入Dir类库了。 Vendor方法也可以支持和import方法一样的基础路径和文件名后缀参数,例 如: Vendor('Zend.Filter.Dir',dirname(__FILE__),'.class.php'); 应用模式 ThinkPHP支持应用模式定义,每个应用模式有自己的定义文件,用于配置当前 模式需要加载的核心文件和配置文件,以及别名定义、行为扩展定义等等。除了 模式定义外,应用自身也可以独立定义自己的模式文件。 如果应用模式涉及到不同的存储类型,例如采用分布式存储等,就需要另外设置 存储类型(STORAGE_TYPE)。不同的存储类型由T hink\StorageT hink\Storage 类及相关 驱动进行支持。 默认情况下的应用模式是普通模式(common),如果要采用其他的应用模式 (当然,前提是已经有定义),必须在入口文件中定义,设置 APP_MODE 常 量即可,例如: // 定义存储类型和应用模式为SAE(用于支持SAE平台) define('STORAGE_TYPE','sae'); define('APP_MODE','sae'); define('APP_PATH','./Application/'); require './ThinkPHP/ThinkPHP.php'; 应用模式的一个典型应用是对分布式平台的支持,对不同的平台定义不同的应用 模式就可以支持。 ThinkPHP3.2.3完全开发手册 - 63 -© 本文档使用 看云 构建 以上代码只是示范,其实SAE平台不需要显式定义,因为系统会自动识别 SAE平台而采用sae模式。 每个入口文件仅能定义一个应用模式,所以,如果需要对相同的应用模块设 置不同的应用模式访问,就可以通过增加入口文件的方式来解决。 每个应用模式可以定义单独的配置文件,一般是 config_模式名称 ,例如,sae 模式下面可以定义: // 应用配置文件 Application/Common/Conf/config_sae.php // 模块配置文件 Application/Home/Conf/config_sae.php config_sae配置文件只会sae模式下面加载,如果不是sae模式则不会加载。 项目编译 应用编译缓存 编译缓存的基础原理是第一次运行的时候把核心需要加载的文件去掉空白和注释 后合并到一个文件中,第二次运行的时候就直接载入编译缓存而无需载入众多的 核心文件。当第二次执行的时候就会根据当前的应用模式直接载入编译过的缓存 文件,从而省去很多IO开销,加快执行速度。 项目编译机制对运行没有任何影响,预编译机制只会执行一次,因此无论在预编 译过程中做了多少复杂的操作,对后面的执行没有任何效率的缺失。 编译缓存文件默认生成在应用目录的Runtime目录下面,我们可以在 Application/Runtime目录下面看到有一个 common~runtime.php 文件,这 ThinkPHP3.2.3完全开发手册 - 64 -© 本文档使用 看云 构建 个就是普通模式的编译缓存文件。如果你当前运行在其他的应用模式下面,那么 编译缓存文件就是: 应用模式名~runtime.php 例如,如果你当前用的是SAE模式,那么生成的编译缓存文件则会变成 sae~runtime.php 。 普通模式的编译缓存的内容包括:系统函数库、系统基础核心类库、核心行 为类库、项目函数文件,当然这些是可以改变的。 运行Lite文件 运行Lite文件的作用是替换框架的入口文件或者替换应用入口文件,提高运行效 率。因为默认生成的文件名为lite.php,并且是运行时动态生成,因此称之为运运 行Lite文件行Lite文件。 Lite文件的特点包括: 运行时动态生成; 常量定义为针对当前环境; 支持定义需要编译的文件列表; 支持生成Lite文件的名称; 如何生成Lite文件,请参考部署部分的替换入口。 系统流程 ThinkPHP框架开发的应用的标准执行流程如下: 1. 用户URL请求 2. 调用应用入口文件(通常是网站的index.php) 3. 载入框架入口文件(ThinkPHP.php) 4. 记录初始运行时间和内存开销 ThinkPHP3.2.3完全开发手册 - 65 -© 本文档使用 看云 构建 5. 系统常量判断及定义 6. 载入框架引导类(Think\Think)并执行Think::start方法进行应用初始化 7. 设置错误处理机制和自动加载机制 8. 调用Think\Storage类进行存储初始化(由STORAGE_TYPE常量定义存储 类型) 9. 部署模式下如果存在应用编译缓存文件则直接加载(直接跳转到步骤22) 10. 读取应用模式(由APP_MODE常量定义)的定义文件(以下以普通模式为 例说明) 11. 加载当前应用模式定义的核心文件(普通模式是 ThinkPHP/Mode/common.php) 12. 加载惯例配置文件(普通模式是 ThinkPHP/Conf/convention.php) 13. 加载应用配置文件(普通模式是 Application/Common/Conf/config.php) 14. 加载系统别名定义 15. 判断并读取应用别名定义文件(普通模式是 Application/Common/Conf/alias.php) 16. 加载系统行为定义 17. 判断并读取应用行为定义文件(普通模式是 Application/Common/Conf/tags.php) 18. 加载框架底层语言包(普通模式是 ThinkPHP/Lang/zh-cn.php) 19. 如果是部署模式则生成应用编译缓存文件 20. 加载调试模式系统配置文件(ThinkPHP/Conf/debug.php) 21. 判断并读取应用的调试配置文件(默认是 Application/Common/Conf/debug.php) 22. 判断应用状态并读取状态配置文件(如果APP_STATUS常量定义不为空的 话) 23. 检测应用目录结构并自动生成(如果CHECK_APP_DIR配置开启并且 RUNTIME_PATH目录不存在的情况下) 24. 调用Think\App类的run方法启动应用 25. 应用初始化(app_init)标签位侦听并执行绑定行为 ThinkPHP3.2.3完全开发手册 - 66 -© 本文档使用 看云 构建 26. 判断并加载动态配置和函数文件 27. 调用Think\Dispatcher::dispatch方法进行URL请求调度 28. 自动识别兼容URL模式和命令行模式下面的$_SERVER['PATH_INFO']参数 29. 检测域名部署以及完成模块和控制器的绑定操作 (APP_SUB_DOMAIN_DEPLOY参数开启) 30. 分析URL地址中的PATH_INFO信息 31. 获取请求的模块信息 32. 检测模块是否存在和允许访问 33. 判断并加载模块配置文件、别名定义、行为定义及函数文件 34. 判断并加载模块的动态配置和函数文件 35. 模块的URL模式判断 36. 模块的路由检测(URL_ROUTER_ON开启) 37. PATH_INFO处理(path_info)标签位侦听并执行绑定行为 38. URL后缀检测(URL_DENY_SUFFIX以及URL_HTML_SUFFIX处理) 39. 获取当前控制器和操作,以及URL其他参数 40. URL请求调度完成(url_dispatch)标签位侦听并执行绑定行为 41. 应用开始(app_begin)标签位侦听并执行绑定行为 42. 调用SESSION_OPTIONS配置参数进行Session初始化(如果不是命令行 模式) 43. 根据请求执行控制器方法 44. 如果控制器不存在则检测空控制器是否存在 45. 控制器开始(action_begin)标签位侦听并执行绑定行为 46. 默认调用系统的ReadHtmlCache行为读取静态缓存(HTML_CACHE_ON 参数开启) 47. 判断并调用控制器的_initialize初始化方法 48. 判断操作方法是否存在,如果不存在则检测是否定义空操作方法 49. 判断前置操作方法是否定义,有的话执行 50. Action参数绑定检测,自动匹配操作方法的参数 51. 如果有模版渲染(调用控制器display方法) 52. 视图开始(view_begin)标签位侦听并执行绑定行为 ThinkPHP3.2.3完全开发手册 - 67 -© 本文档使用 看云 构建 53. 调用Think\View的fetch方法解析并获取模版内容 54. 自动识别当前主题以及定位模版文件 55. 视图解析(view_parse)标签位侦听并执行绑定行为 56. 默认调用内置ParseTemplate行为解析模版(普通模式下面) 57. 模版引擎解析模版内容后生成模版缓存 58. 模版过滤替换(template_filter)标签位侦听并执行绑定行为 59. 默认调用系统的ContentReplace行为进行模版替换 60. 输出内容过滤(view_filter)标签位侦听并执行绑定行为 61. 默认调用系统的WriteHtmlCache行为写入静态缓存(HTML_CACHE_ON 参数开启) 62. 调用Think\View类的render方法输出渲染内容 63. 视图结束(view_end)标签位侦听并执行绑定行为 64. 判断后置操作方法是否定义,有的话执行 65. 控制器结束(action_end)标签位侦听并执行绑定行为 66. 应用结束(app_end)标签位侦听并执行绑定行为 67. 执行系统的ShowPageTrace行为(SHOW_PAGE_TRACE参数开启并且不 是AJAX请求) 68. 日志信息存储写入 如果你绑定了更多的应用行为的话,流程可能会更加复杂。 如果是部署模式下面的第二次请求的话,上面的流程中的步骤10~21是可以 省略的。 路由 利用路由功能,可以让你的URL地址更加简洁和优雅。ThinkPHP支持对模块的 URL地址进行路由操作(路由功能是针对PATHINFO模式或者兼容URL而设计 的,暂时不支持普通URL模式)。 ThinkPHP3.2.3完全开发手册 - 68 -© 本文档使用 看云 构建 ThinkPHP的路由功能包括: 正则路由 规则路由 静态路由(URL映射) 闭包支持 路由定义 启用路由 要使用路由功能,前提是你的URL支持PATH_INFO(或者兼容URL模式也可 以,采用普通URL模式的情况下不支持路由功能),并且在应用(或者模块)配 置文件中开启路由: // 开启路由 'URL_ROUTER_ON' => true, 路由功能可以针对模块,也可以针对全局,针对模块的路由则需要在模块配 置文件中开启和设置路由,如果是针对全局的路由,则是在公共模块的配置 文件中开启和设置(后面我们以模块路由定义为例)。 然后就是配置路由规则了,在模块的配置文件中使用URL_ROUT E_RULESURL_ROUT E_RULES参 数进行配置,配置格式是一个数组,每个元素都代表一个路由规则,例如: ThinkPHP3.2.3完全开发手册 - 69 -© 本文档使用 看云 构建 'URL_ROUTE_RULES'=>array( 'news/:year/:month/:day' => array('News/archive', 'status=1'), 'news/:id' => 'News/read', 'news/read/:id' => '/news/:1', ), 系统会按定义的顺序依次匹配路由规则,一旦匹配到的话,就会定位到路由定义 中的控制器和操作方法去执行(可以传入其他的参数),并且后面的规则不会继 续匹配。 路由定义 路由规则的定义格式为: '路由表达式'=>'路由地址和传入参数''路由表达式'=>'路由地址和传入参数' 或者:array('路由表达式','路由地址','传入参数')array('路由表达式','路由地址','传入参数') 模块路由和全局路由配置的区别在于,全局路由的路由地址必须包含模块。 路由表达式 路由表达式包括规则路由和正则路由的定义表达式,只能使用字符串。 表达式表达式 示例示例 正则表达式 /^blog\/(\d+)$/ 规则表达式 blog/:id 详细的规则路由和正则路由表达式的定义方法参考后面的章节。 路由地址 路由地址(可以支持传入额外参数)表示前面的路由表达式需要路由到的地址 (包括内部地址和外部地址),并且允许隐式传入URL里面没有的一些参数,这 里允许使用字符串或者数组方式定义,特殊情况下还可以采用闭包函数定义路由 ThinkPHP3.2.3完全开发手册 - 70 -© 本文档使用 看云 构建 功能,支持下面6种方式定义: 定义方式定义方式 定义格式定义格式 方式1:路由到内部地址 (字符串) '[控制器/操作]?额外参数1=值1&额外参数 2=值2...' 方式2:路由到内部地址 (数组)参数采用字符串方 式 array('[控制器/操作]','额外参数1=值1&额 外参数2=值2...') 方式3:路由到内部地址 (数组)参数采用数组方式 array('[控制器/操作]',array('额外参数 1'=>'值1','额外参数2'=>'值2'...)[,路由参 数]) 方式4:路由到外部地址 (字符串)301重定向 '外部地址' 方式5:路由到外部地址 (数组)可以指定重定向代 码 array('外部地址','重定向代码'[,路由参数]) 方式6:闭包函数 function($name){ echo 'Hello,'.$name;} 如果你定义的是全局路由(在公共模块的配置文件中定义),那么路由地址的定 义格式中需要增加模块名,例如: 'blog/:id'=>'Home/blog/read' // 表示路由到Home模块的blog控制器的 read操作方法 如果路由地址以“/”或者“http”开头则会认为是一个重定向地址或者外部地 址,例如: 'blog/:id'=>'/blog/read/id/:1' ThinkPHP3.2.3完全开发手册 - 71 -© 本文档使用 看云 构建 和 'blog/:id'=>'blog/read' 虽然都是路由到同一个地址,但是前者采用的是301重定向的方式路由跳转,这 种方式的好处是URL可以比较随意(包括可以在URL里面传入更多的非标准格式 的参数),而后者只是支持模块和操作地址。 举个例子,如果我们希望 avatar/123 重定向到 /member/avatar/id/123_small 的话,只能使用: 'avatar/:id'=>'/member/avatar/id/:1_small' 路由地址采用重定向地址的话,如果要引用动态变量,也是采用 :1、:2 的方 式。 采用重定向到外部地址通常对网站改版后的URL迁移过程非常有用,例如: 'blog/:id'=>'http://blog.thinkphp.cn/read/:1' 表示当前网站(可能是http://thinkphp.cn)的 blog/123 地址会直接重定向 到 http://blog.thinkphp.cn/read/123 。 默认情况下,外部地址的重定向采用301重定向,如果希望采用其它的,可以使 用: 'blog/:id'=>array('http://blog.thinkphp.cn/read/:1',302); 在路由跳转的时候支持额外传入参数对(额外参数指的是不在URL里面的参数, 隐式传入需要的操作中,有时候能够起到一定的安全防护作用,后面我们会提 到),支持 额外参数1=值1&额外参数2=值2 或者 ThinkPHP3.2.3完全开发手册 - 72 -© 本文档使用 看云 构建 array('额外参数1'=>'值1','额外参数2'=>'值2'...) 这样的写法,可以参考不同 的定义方式选择。例如: 'blog/:id'=>'blog/read?status=1&app_id=5', 'blog/:id'=>array('blog/read?status=1&app_id=5'), 'blog/:id'=>array('blog/read','status=1&app_id=5'), 'blog/:id'=>array('blog/read',array('status'=>1,'app_id'=>5)), 上面的路由规则定义中额外参数的传值方式都是等效的。 status 和 app_id 参 数都是URL里面不存在的,属于隐式传值,当然并不一定需要用到,只是在需要 的时候可以使用。 路由参数 当路由地址采用数组方式定义的时候,还可以传入额外的路由参数。 这些参数的作用是限制前面定义的路由规则的生效条件。 限制URL后缀 例如: 'blog/:id'=>array('blog/read','status=1&app_id=5',array('ext'=>'htm l')), 就可以限制html后缀访问该路由规则才能生效。 限制请求类型 例如: 'blog/:id'=>array('blog/read','status=1&app_id=5',array('method'=> 'get')), ThinkPHP3.2.3完全开发手册 - 73 -© 本文档使用 看云 构建 就限制了只有GET请求该路由规则才能生效。 自定义检测 支持自定义检测,例如: 例如: 'blog/:id'=>array('blog/read','status=1&app_id=5',array('callback'=> 'checkFun')), 就可以自定义checkFun函数来检测是否生效,如果函数返回false则表示不生 效。 规则路由 规则路由是一种比较容易理解的路由定义方式,采用ThinkPHP设计的规则表达 式来定义。 规则表达式 规则表达式通常包含静态地址和动态地址,或者两种地址的结合,例如下面都属 于有效的规则表达式: 'my' => 'Member/myinfo', // 静态地址路由 'blog/:id' => 'Blog/read', // 静态地址和动态地址结合 'new/:year/:month/:day'=>'News/read', // 静态地址和动态地址结合 ':user/:blog_id' =>'Blog/read',// 全动态地址 规则表达式的定义始终以“/”为参数分割符,不受 URL_PATHINFO_DEPR 设置的影响 每个参数中以“:”开头的参数都表示动态参数,并且会自动对应一个GET参 ThinkPHP3.2.3完全开发手册 - 74 -© 本文档使用 看云 构建 数,例如 :id 表示该处匹配到的参数可以使用 $_GET['id'] 方式获取, :year 、 :month 、 :day 则分别对应 $_GET['year'] 、 $_GET['month'] 和 $_GET['day'] 。 数字约束 支持对变量的类型检测,但仅仅支持数字类型的约束定义,例如 'blog/:id\d'=>'Blog/read', 表示只会匹配数字参数,如果你需要更加多的变量类型检测,请使用正则表达式 定义来解决。 目前不支持长度约束,需要的话采用正则定义解决 函数支持 可以支持对路由变量的函数过滤,例如: 'blog/:id\d|md5'=>'Blog/read', 表示对匹配到的id变量进行md5处理,也就是说,实际传入read操作方法的 $_GET['id'] 其实是 md5($_GET['id']) 。 注意:不支持对变量使用多次函数处理和函数额外参数传入。 可选定义 支持对路由参数的可选定义,例如: 'blog/:year\d/[:month\d]'=>'Blog/archive', [:month\d] 变量用[ ]包含起来后就表示该变量是路由匹配的可选变量。 ThinkPHP3.2.3完全开发手册 - 75 -© 本文档使用 看云 构建 以上定义路由规则后,下面的URL访问地址都可以被正确的路由匹配: http://serverName/index.php/Home/blog/2013 http://serverName/index.php/Home/blog/2013/12 采用可选变量定义后,之前需要定义两个或者多个路由规则才能处理的情况可以 合并为一个路由规则。 可选参数只能放到路由规则的最后,如果在中间使用了可选参数的话,后面 的变量都会变成可选参数。 规则排除 非数字变量支持简单的排除功能,主要是起到避免解析混淆的作用,例如: 'news/:cate^add-edit-delete'=>'News/category' 因为规则定义的局限性,恰巧我们的路由规则里面的news和实际的news模块 是相同的命名,而 :cate 并不能自动区分当前URL里面的动态参数是实际的操作 名还是路由变量,所以为了避免混淆,我们需要对路由变量cate进行一些排除 以帮助我们进行更精确的路由匹配,格式 ^add-edit-delete 表示,匹配除了 add edit 和delete之外的所有字符串,我们建议更好的方式还是改进你的路由 规则,避免路由规则和模块同名的情况存在,例如 'new/:cate'=>'News/category' 就可以更简单的定义路由规则了。 完全匹配 规则匹配检测的时候只是对URL从头开始匹配,只要URL地址包含了定义的路由 规则就会匹配成功,如果希望完全匹配,可以使用$符号,例如: ThinkPHP3.2.3完全开发手册 - 76 -© 本文档使用 看云 构建 'new/:cate$'=> 'News/category' http://serverName/index.php/Home/new/info 会匹配成功,而 http://serverName/index.php/Home/new/info/2 则不会匹配成功。 如果是采用 'new/:cate'=> 'News/category' 方式定义的话,则两种方式的URL访问都可以匹配成功。 完全匹配的路由规则中如果使用可选参数的话将会无效。 正则路由 正则路由也就是采用正则表达式定义路由的一种方式,依靠强大的正则表达式, 能够定义更灵活的路由规则。 路由表达式支持的正则定义必须以“/”开头,否则就视为规则表达式。也就是 说如果采用: '#^blog\/(\d+)$#' => 'Blog/read/id/:1' 方式定义的正则表达式不会被支持,而会被认为是规则表达式进行解析,从而无 法正确匹配。 ThinkPHP3.2.3完全开发手册 - 77 -© 本文档使用 看云 构建 下面是一种正确的正则路由定义: '/^new\/(\d{4})\/(\d{2})$/' => 'News/achive?year=:1&month=:2', 对于正则表达式中的每个变量(即正则规则中的子模式)部分,如果需要在 后面的路由地址中引用,可以采用:1、:2这样的方式,序号就是子模式的序 号。 正则定义也支持函数过滤处理,例如: '/^new\/(\d{4})\/(\d{2})$/' => 'News/achive?year=:1|format_year& month=:2', 其中 year=:1|format_year 就表示对匹配到的变量进行format_year函数处理 (假设format_year是一个用户自定义函数)。 更多的关于如何定义正则表达式就不在本文的描述范畴了。 静态路由 静态路由其实属于规则路由的静态简化版(又称为URL映射),路由定义中不包 含动态参数,静态路由不需要遍历路由规则而是直接定位,因此效率较高,但作 用也有限。 如果我们定义了下面的静态路由 ThinkPHP3.2.3完全开发手册 - 78 -© 本文档使用 看云 构建 'URL_ROUTER_ON' => true, 'URL_MAP_RULES'=>array( 'new/top' => 'news/index?type=top' ) 注意:为了不影响动态路由的遍历效率,静态路由采用URL_MAP_RULES定 义和动态路由区分开来 定义之后,如果我们访问: http://serverName/Home/new/top 其实是访问: http://serverName/Home/news/index/type/top 静态路由是完整匹配,所以如果访问: http://serverName/Home/new/top/var/test 尽管前面也有 new/top ,但并不会被匹配到 news/index/type/top 。 静态路由定义不受URL后缀影响,例如: http://serverName/Home/new/top.html 也可以正常访问。 静态路由的路由地址 只支持字符串,格式: [控制器/操作?]参数1=值1&参数2=值2 闭包支持 闭包定义 我们可以使用闭包的方式定义一些特殊需求的路由,而不需要执行控制器的操作 方法了,例如: ThinkPHP3.2.3完全开发手册 - 79 -© 本文档使用 看云 构建 'URL_ROUTE_RULES'=>array( 'test' => function(){ echo 'just test'; }, 'hello/:name' => function($name){ echo 'Hello,'.$name; } ) 参数传递 闭包定义的参数传递在规则路由和正则路由的两种情况下有所区别。 规则路由 规则路由的参数传递比较简单: 'hello/:name' => function($name){ echo 'Hello,'.$name; } 规则路由中定义的动态变量的名称 就是闭包函数中的参数名称,不分次序。 因 此,如果我们访问的URL地址是: http://serverName/Home/hello/thinkphp 则浏览器输出的结果是: Hello,thinkphp 如果多个参数可以使用: ThinkPHP3.2.3完全开发手册 - 80 -© 本文档使用 看云 构建 'blog/:year/:month' => function($year,$month){ echo 'year='.$year.'&month='.$month; } 正则路由 如果是正则路由的话,闭包函数中的参数就以正则中出现的参数次序来传递,例 如: '/^new\/(\d{4})\/(\d{2})$/' => function($year,$month){ echo 'year='.$year.'&month='.$month; } 如果我们访问: http://serverName/Home/new/2013/03 浏览器输出结果 是: year=2013&month=03 继续执行 默认的情况下,使用闭包定义路由的话,一旦匹配到路由规则,执行完闭包方法 之后,就会中止后续执行。如果希望闭包函数执行后,后续的程序继续执行,可 以在闭包函数中使用布尔类型的返回值,例如: 'hello/:name' => function($name){ echo 'Hello,'.$name.'
'; $_SERVER['PATH_INFO'] = 'blog/read/name/'.$name; return false; } 该路由定义中的闭包函数首先执行了一段输出代码,然后重新设置了 ThinkPHP3.2.3完全开发手册 - 81 -© 本文档使用 看云 构建 $_SERVER['PATH_INFO'] 变量,交给后续的程序继续执行,因为返回值是 false,所以会继续执行控制器和操作的检测,从而会执行Blog控制器的read操 作方法。 假设blog控制器中的read操作方法代码如下: public function read($name){ echo 'read,'.$name.'!
'; } 如果我们访问的URL地址是: http://serverName/Home/hello/thinkphp 则浏览器输出的结果是: Hello,thinkphp read,thinkphp! 实例说明 我们已经了解了如何定义路由规则,下面我们来举个例子加深印象。 假设我们定义了News控制器如下(代码实现仅供参考): ThinkPHP3.2.3完全开发手册 - 82 -© 本文档使用 看云 构建 namespace Home\Controller; use Think\Controller; class NewsController extends Controller{ public function read(){ $New = M('New'); if(isset($_GET['id'])) { // 根据id查询结果 $data = $New->find($_GET['id']); }elseif(isset($_GET['name'])){ // 根据name查询结果 $data = $New->getByName($_GET['name']); } $this->data = $data; $this->display(); } public function archive(){ $New = M('New'); $year = $_GET['year']; $month = $_GET['month']; $begin_time = strtotime($year . $month . "01"); $end_time = strtotime("+1 month", $begin_time); $map['create_time'] = array(array('gt',$begin_time),array('lt',$e nd_time)); $map['status'] = 1; $list = $New->where($map)->select(); $this->list = $list; $this->display(); } } 定义路由规则如下: ThinkPHP3.2.3完全开发手册 - 83 -© 本文档使用 看云 构建 'URL_ROUTER_ON' => true, //开启路由 'URL_ROUTE_RULES' => array( //定义路由规则 'new/:id\d' => 'News/read', 'new/:name' => 'News/read', 'new/:year\d/:month\d' => 'News/archive', ), 然后,我们访问: http://serverName/index.php/Home/new/8 会匹配到第一个路由规则,实际执行的效果等效于访问: http://serverName/index.php/Home/News/read/id/8 当访问: http://serverName/index.php/Home/new/hello 会匹配到第二个路由规则,实际执行的效果等效于访问: http://serverName/index.php/Home/News/read/name/hello 那么如果访问: http://serverName/index.php/Home/new/2012/03 是否会匹配第三个路由规则呢?我们期望的实际执行的效果能够等效于访问: http://serverName/index.php/Home/News/archive/year/2012/month/ 03 事实上却没有,因为 http://serverName/index.php/Home/new/2012/ 这 个URL在进行路由匹配过程中已经优先匹配到了第一个路由规则了,把2012当 成id的值传入了,这种情况属于路由规则的冲突,解决办法有两个: 1、调整定义顺序1、调整定义顺序 路由定义改成: ThinkPHP3.2.3完全开发手册 - 84 -© 本文档使用 看云 构建 'URL_ROUTE_RULES' => array( //定义路由规则 'new/:year\d/:month\d' => 'News/archive', 'new/:id\d' => 'News/read', 'new/:name' => 'News/read', ), 接下来,当我们再次访问: http://serverName/index.php/Home/new/2012/03 的时候,达到了预期的访问效果。所以如果存在可能规则冲突的情况,尽量把规 则复杂的规则定义放到前面,确保最复杂的规则可以优先匹配到。但是如果路由 规则定义多了之后,仍然很容易混淆,所以需要寻找更好的解决办法。 2、利用完全匹配功能2、利用完全匹配功能 现在我们来利用路由的完全匹配定义功能,把路由定义改成: 'URL_ROUTE_RULES' => array( //定义路由规则 'new/:id\d$' => 'News/read', 'new/:name$' => 'News/read', 'new/:year\d/:month\d$' => 'News/archive', ), 在规则最后加上$符号之后,表示完整匹配当前的路由规则,就可以避免规则定 义的冲突了。对于规则路由来说,简单的理解就是URL里面的参数数量或者类型 约束要完全一致。 所以,如果我们访问 http://serverName/index.php/Home/new/2012/03/01 的话,是不会匹配成功任何一条路由的。 3、利用正则路由3、利用正则路由 当然,解决问题的办法总是不止一种,对于复杂的情况,我们不要忘了使用正则 ThinkPHP3.2.3完全开发手册 - 85 -© 本文档使用 看云 构建 路由规则定义,在你找不到解决方案的时候,正则路由总能帮到你。 要实现上 面的同样路由功能的话,还可以用下面的规则定义: 'URL_ROUTE_RULES' => array( //定义路由规则 '/^new\/(\d+)$/' => 'News/read?id=:1', '/^new\/(\w+)$/' => 'News/read?name=:1', '/^new\/(\d{4})\/(\d{2})$/' => 'News/achive?year=:1&month=:2' , ), 控制器 控制器定义 控制器和操作 一般来说,ThinkPHP的控制器是一个类,而操作则是控制器类的一个公共方公共方 法法。 下面就是一个典型的控制器类的定义: ThinkPHP3.2.3完全开发手册 - 86 -© 本文档使用 看云 构建 'Action', // 操作方法后缀 设置操作方法的后缀为Action,这样,控制器的操作方法定义调整为: ThinkPHP3.2.3完全开发手册 - 89 -© 本文档使用 看云 构建 2, ThinkPHP3.2.3完全开发手册 - 91 -© 本文档使用 看云 构建 控制器文件的位置放置如下: ├─Controller 访问控制器 │ ├─User User分级(组) │ │ ├─UserTypeController.class.php │ │ ├─UserAuthController.class.php │ ... │ ├─Admin Admin分级(组) │ │ ├─UserController.class.php │ │ ├─ConfigController.class.php │ ... 多级控制器中的命名空间需要这样定义: '; } public function index(){ echo 'index
'; } //后置操作方法 public function _after_index(){ echo 'after
'; } } 如果我们访问 http://serverName/index.php/Home/Index/index 结果会输出 before index after 前置和后置操作的注意事项如下:前置和后置操作的注意事项如下: 1. 如果当前的操作并没有定义操作方法,而是直接渲染模板文件,那么如果定 义了前置和后置方法的话,依然会生效。真正有模板输出的可能仅仅是当前的操 作,前置和后置操作一般情况是没有任何输出的。 2. 需要注意的是,在有些方法里面使用了exit或者错误输出之类的话 有可能不 会再执行后置方法了。例如,如果在当前操作里面调用了控制器类的error方 法,那么将不会再执行后置操作,但是不影响success方法的后置方法执行。 ThinkPHP3.2.3完全开发手册 - 95 -© 本文档使用 看云 构建 Action参数绑定 Action参数绑定是通过直接绑定URL地址中的变量作为操作方法的参数,可以 简化方法的定义甚至路由的解析。 Action参数绑定功能默认是开启的,其原理是把URL中的参数(不包括模块、 控制器和操作名)和操作方法中的参数进行绑定。 要启用参数绑定功能,首先确保你开启了 URL_PARAMS_BIND 设置: 'URL_PARAMS_BIND' => true, // URL变量绑定到操作方法作为参 数 参数绑定有两种方式:按照变量名绑定和按照变量顺序绑定按照变量名绑定和按照变量顺序绑定。 按变量名绑定 默认的参数绑定方式是按照变量名进行绑定,例如,我们给Blog控制器定义了 两个操作方法read和archive方法,由于read操作需要指定一个id参 数,archive方法需要指定年份(year)和月份(month)两个参数,那么我们 可以如下定义: ThinkPHP3.2.3完全开发手册 - 96 -© 本文档使用 看云 构建 namespace Home\Controller; use Think\Controller; class BlogController extends Controller{ public function read($id){ echo 'id='.$id; } public function archive($year='2013',$month='01'){ echo 'year='.$year.'&month='.$month; } } 注意这里的操作方法并没有具体的业务逻辑,只是简单的示范。 URL的访问地址分别是: http://serverName/index.php/Home/Blog/read/id/5 http://serverName/index.php/Home/Blog/archive/year/2013/mont h/11 两个URL地址中的id参数和year和month参数会自动和read操作方法以及 archive操作方法的同名参数绑定。 变量名绑定不一定由访问URL决定,路由地址也能起到相同的作用 输出的结果依次是: id=5 year=2013&month=11 按照变量名进行参数绑定的参数必须和URL中传入的变量名称一致,但是参数顺 序不需要一致。也就是说 ThinkPHP3.2.3完全开发手册 - 97 -© 本文档使用 看云 构建 http://serverName/index.php/Home/Blog/archive/month/11/year/2 013 和上面的访问结果是一致的,URL中的参数顺序和操作方法中的参数顺序都可以 随意调整,关键是确保参数名称一致即可。 如果使用下面的URL地址进行访问,参数绑定仍然有效: http://serverName/index.php?s=/Home/Blog/read/id/5 http://serverName/index.php?s=/Home/Blog/archive/year/2013/m onth/11 http://serverName/index.php?c=Blog&a=read&id=5 http://serverName/index.php?c=Blog&a=archive&year=2013&mon th=11 如果用户访问的URL地址是(至于为什么会这么访问暂且不提): http://serverName/index.php/Home/Blog/read/ 那么会抛出下面的异常提示: 参数错误:id 报错的原因很简单,因为在执行read操作方法的时候,id参数是必须传入参数 的,但是方法无法从URL地址中获取正确的id参数信息。由于我们不能相信用户 的任何输入,因此建议你给read方法的id参数添加默认值,例如: public function read($id=0){ echo 'id='.$id; } 这样,当我们访问 http://serverName/index.php/Home/Blog/read/ 的时 候 就会输出 ThinkPHP3.2.3完全开发手册 - 98 -© 本文档使用 看云 构建 id=0 当我们访问 http://serverName/index.php/Home/Blog/archive/ 的时候, 输出: year=2013&month=01 始终给操作方法的参数定义默认值是一个避免报错的好办法 按变量顺序绑定 第二种方式是按照变量的顺序绑定,这种情况下URL地址中的参数顺序非常重 要,不能随意调整。要按照变量顺序进行绑定,必须先设置 URL_PARAMS_BIND_TYPE 为1: 'URL_PARAMS_BIND_TYPE' => 1, // 设置参数绑定按照变量顺序绑定 操作方法的定义不需要改变,URL的访问地址分别改成: http://serverName/index.php/Home/Blog/read/5 http://serverName/index.php/Home/Blog/archive/2013/11 输出的结果依次是: id=5 year=2013&month=11 这个时候如果改成 http://serverName/index.php/Home/Blog/archive/11/2013 ThinkPHP3.2.3完全开发手册 - 99 -© 本文档使用 看云 构建 输出的结果就变成了: year=11&month=2013 显然就有问题了,所以不能随意调整参数在URL中的传递顺序,要确保和你的操 作方法定义顺序一致。 可以看到,这种参数绑定的效果有点类似于简单的规则路由。 按变量顺序绑定的方式目前仅对PATHINFO地址有效,所以下面的URL访问参 数绑定会失效: http://serverName/index.php?c=Blog&a=read&id=5 http://serverName/index.php?c=Blog&a=archive&year=2013&mon th=11 但是,兼容模式URL地址访问依然有效: http://serverName/index.php?s=/Home/Blog/read/5 http://serverName/index.php?s=/Home/Blog/archive/2013/11 如果你的操作方法定义都不带任何参数或者不希望使用该功能的话,可以关闭参 数绑定功能: 'URL_PARAMS_BIND' => false 伪静态 URL伪静态通常是为了满足更好的SEO效果,ThinkPHP支持伪静态URL设置, ThinkPHP3.2.3完全开发手册 - 100 -© 本文档使用 看云 构建 可以通过设置 URL_HTML_SUFFIX 参数随意在URL的最后增加你想要的静态后 缀,而不会影响当前操作的正常执行。例如,我们设置 'URL_HTML_SUFFIX'=>'shtml' 的话,我们可以把下面的URL http://serverName/Home/Blog/read/id/1 变成 http://serverName/Home/Blog/read/id/1.shtml 后者更具有静态页面的URL特征,但是具有和前面的URL相同的执行效果,并且 不会影响原来参数的使用。 默认情况下,伪静态的设置为 html ,如果我们设置伪静态后缀为空, 'URL_HTML_SUFFIX'=>'' 则可以支持所有的静态后缀,并且会记录当前的伪静态后缀到常量 __EXT__ , 但不会影响正常的页面访问。 例如: http://serverName/Home/blog/3.html http://serverName/Home/blog/3.shtml http://serverName/Home/blog/3.xml http://serverName/Home/blog/3.pdf 都可以正常访问,如果要获取当前的伪静态后缀,通过常量 __EXT__ 获取即 可。 如果希望支持多个伪静态后缀,可以直接设置如下: // 多个伪静态后缀设置 用|分割 'URL_HTML_SUFFIX' => 'html|shtml|xml' ThinkPHP3.2.3完全开发手册 - 101 -© 本文档使用 看云 构建 那么,当访问 http://serverName/Home/blog/3.pdf 的时候会报系统错 误。 可以设置禁止访问的URL后缀,例如: 'URL_DENY_SUFFIX' => 'pdf|ico|png|gif|jpg', // URL禁止访问的后缀设 置 如果访问 http://serverName/Home/blog/3.pdf 就会直接返回404错误。 注意:注意: URL_DENY_SUFFIX 的优先级比 URL_HTML_SUFFIX 要高。 URL大小写 系统默认的规范是根据URL里面的模块名、控制器名来定位到具体的控制器类 的,从而执行控制器类的操作方法。 以URL访问 http://serverName/index.php/Home/Index/index 为例,其 实访问的控制器类文件是: Application/Home/Controller/IndexController.class.php 如果是Windows环境,无论大小写如何都能定位到 IndexController.class.php 文件,所以下面的访问都是有效的: http://serverName/index.php/Home/Index/index http://serverName/index.php/Home/index/index http://serverName/index.php/home/index/index ThinkPHP3.2.3完全开发手册 - 102 -© 本文档使用 看云 构建 如果在Linux环境下面,一旦大小写不一致,就会发生URL里面使用小写模块名 不能找到模块类的情况。例如在Linux环境下面,我们访问 http://serverName/index.php/home/index/index 其实请求的控制器文件 是 Application/home/Controller/indexController.class.php 因为,我们定义的控制器类是IndexController而不是indexController(参考 ThinkPHP的命名规范),由于Linux的文件特性,其实是不存在 indexController控制器文件的,就会出现Index控制器不存在的错误,这样的 问题会造成用户体验的下降。 但是系统本身提供了一个不区分URL大小写的解决方案,可以通过配置简单实 现。 只要在项目配置中,增加: 'URL_CASE_INSENSITIVE' =>true 配置好后,即使是在Linux环境下面,也可以实现URL访问不再区分大小写了。 http://serverName/index.php/Home/Index/index // 将等效于 http://serverName/index.php/home/index/index 这里需要注意一个地方,一旦开启了不区分URL大小写后,如果我们要访问类似 UserTypeController的控制器,那么正确的URL访问应该是: ThinkPHP3.2.3完全开发手册 - 103 -© 本文档使用 看云 构建 // 正确的访问地址 http://serverName/index.php/home/user_type/index // 错误的访问地址(linux环境下) http://serverName/index.php/home/usertype/index 利用系统提供的U方法(后面一章URL生成会告诉你如何生成)可以为你自动生 成相关的URL地址。 如果设置 'URL_CASE_INSENSITIVE' =>false 的话,URL就又变成: http://serverName/index.php/Home/UserType/add 注意:URL不区分大小写并不会改变系统的命名规范,并且只有按照系统的 命名规范后才能正确的实现URL不区分大小写。 URL生成 为了配合所使用的URL模式,我们需要能够动态的根据当前的URL设置生成对应 的URL地址,为此,ThinkPHP内置提供了U方法,用于URL的动态生成,可以 确保项目在移植过程中不受环境的影响。 定义规则 U方法的定义规则如下(方括号内参数根据实际应用决定): U('地址表达式',['参数'],['伪静态后缀'],['显示域名'])U('地址表达式',['参数'],['伪静态后缀'],['显示域名']) 地址表达式 ThinkPHP3.2.3完全开发手册 - 104 -© 本文档使用 看云 构建 地址表达式的格式定义如下: [模块/控制器/操作#锚点@域名]?参数1=值1&参数2=值2... 如果不定义模块的话 就表示当前模块名称,下面是一些简单的例子: U('User/add') // 生成User控制器的add操作的URL地址 U('Blog/read?id=1') // 生成Blog控制器的read操作 并且id为1的URL地址 U('Admin/User/select') // 生成Admin模块的User控制器的select操作的 URL地址 参数 U方法的第二个参数支持数组和字符串两种定义方式,如果只是字符串方式的参 数可以在第一个参数中定义,例如: U('Blog/cate',array('cate_id'=>1,'status'=>1)) U('Blog/cate','cate_id=1&status=1') U('Blog/cate?cate_id=1&status=1') 三种方式是等效的,都是生成Blog控制器的cate操作 并且 cate_id 为1 status 为1的URL地址。 但是不允许使用下面的定义方式来传参数 U('Blog/cate/cate_id/1/status/1'); 伪静态后缀 U函数会自动识别当前配置的伪静态后缀,如果你需要指定后缀生成URL地址的 话,可以显式传入,例如: ThinkPHP3.2.3完全开发手册 - 105 -© 本文档使用 看云 构建 U('Blog/cate','cate_id=1&status=1','xml'); 自动识别 根据项目的不同URL设置,同样的U方法调用可以智能地对应产生不同的URL地 址效果,例如针对: U('Blog/read?id=1'); 这个定义为例。 如果当前URL设置为普通模式的话,最后生成的URL地址是: http://serverName/index.php?m=Blog&a=read&id=1 如果当前URL设置为PATHINFO模式的话,同样的方法最后生成的URL地址 是: http://serverName/index.php/Home/Blog/read/id/1 如果当前URL设置为REWRITE模式的话,同样的方法最后生成的URL地址是: http://serverName/Home/Blog/read/id/1 如果当前URL设置为REWRITE模式,并且设置了伪静态后缀为.html的话,同 样的方法最后生成的URL地址是: http://serverName/Home/Blog/read/id/1.html 如果开启了 URL_CASE_INSENSITIVE ,则会统一生成小写的URL地址。 ThinkPHP3.2.3完全开发手册 - 106 -© 本文档使用 看云 构建 生成路由地址 U方法还可以支持路由,如果我们定义了一个路由规则为: 'news/:id\d'=>'News/read' 那么可以使用 U('/news/1'); 最终生成的URL地址是: http://serverName/index.php/Home/news/1 注意:如果你是在模板文件中直接使用U方法的话,需要采用 {:U('参数1', '参 数2'…)} 的方式,具体参考模板的使用函数内容。 域名支持 如果你的应用涉及到多个子域名的操作地址,那么也可以在U方法里面指定需要 生成地址的域名,例如: U('Blog/read@blog.thinkphp.cn','id=1'); @后面传入需要指定的域名即可。 系统会自动判断当前是否SSL协议,生成 https:// 。 此外,U方法的第4个参数如果设置为true,表示自动识别当前的域名,并且会 自动根据子域名部署设置 APP_SUB_DOMAIN_DEPLOY 和 APP_SUB_DOMAIN_RULES 自动匹配生成当前地址的子域名。 ThinkPHP3.2.3完全开发手册 - 107 -© 本文档使用 看云 构建 锚点支持 U函数可以直接生成URL地址中的锚点,例如: U('Blog/read#comment?id=1'); 生成的URL地址可能是: http://serverName/index.php/Home/Blog/read/id/1#comment AJAX返回 ThinkPHP可以很好的支持AJAX请求,系统的 \Think\Controller 类提供了 ajaxReturn 方法用于AJAX调用后返回数据给客户端。并且支持JSON、 JSONP、XML和EVAL四种方式给客户端接受数据,并且支持配置其他方式的 数据格式返回。 ajaxReturn方法调用示例: $data = 'ok'; $this->ajaxReturn($data); 支持返回数组数据: $data['status'] = 1; $data['content'] = 'content'; $this->ajaxReturn($data); 默认配置采用JSON格式返回数据(通过配置DEFAULT_AJAX_RETURN进行设 ThinkPHP3.2.3完全开发手册 - 108 -© 本文档使用 看云 构建 置),我们可以指定格式返回,例如: // 指定XML格式返回数据 $data['status'] = 1; $data['content'] = 'content'; $this->ajaxReturn($data,'xml'); 返回数据data可以支持字符串、数字和数组、对象,返回客户端的时候根据不 同的返回格式进行编码后传输。如果是JSON/JSONP格式,会自动编码成 JSON字符串,如果是XML方式,会自动编码成XML字符串,如果是EVAL方式 的话,只会输出字符串data数据。 JSON和JSONP虽然只有一个字母的差别,但其实他们根本不是一回事儿: JSON是一种数据交换格式,而JSONP是一种非官方跨域数据交互协议。一 个是描述信息的格式,一个是信息传递的约定方法。 默认的JSONP格式返回的处理方法是 jsonpReturn ,如果你采用不同的方 法,可以设置: 'DEFAULT_JSONP_HANDLER' => 'myJsonpReturn', // 默认JSONP 格式返回的处理方法 或者直接在页面中用callback参数来指定。 除了上面四种返回类型外,我们还可以通过行为扩展来增加其他类型的支 持,只需要对 ajax_return 标签位进行行为绑定即可。 跳转和重定向 ThinkPHP3.2.3完全开发手册 - 109 -© 本文档使用 看云 构建 页面跳转 在应用开发中,经常会遇到一些带有提示信息的跳转页面,例如操作成功或者操 作错误页面,并且自动跳转到另外一个目标页面。系统的 \Think\Controller 类内置了两个跳转方法success和error,用于页面跳转提示,而且可以支持 ajax提交。 使用方法很简单,举例如下: $User = M('User'); //实例化User对象 $result = $User->add($data); if($result){ //设置成功后跳转页面的地址,默认的返回页面是$_SERVER['HTTP_R EFERER'] $this->success('新增成功', '/User/index'); } else { //错误页面的默认跳转页面是返回前一页,通常不需要设置 $this->error('新增失败'); } success和error方法的第一个参数表示提示信息,第二个参数表示跳转地址, 第三个参数是跳转时间(单位为秒),例如: // 操作完成3秒后跳转到 /Article/index $this->success('操作完成','/Article/index',3); // 操作失败5秒后跳转到 /Article/error $this->error('操作失败','/Article/error',5); 跳转地址是可选的,success方法的默认跳转地址是 $_SERVER["HTTP_REFERER"] ,error方法的默认跳转地址是 javascript:history.back(-1); 。 ThinkPHP3.2.3完全开发手册 - 110 -© 本文档使用 看云 构建 默认的等待时间success方法是1秒,error方法是3秒 success 和 error 方法都可以对应的模板,默认的设置是两个方法对应的模板 都是: //默认错误跳转对应的模板文件 'TMPL_ACTION_ERROR' => THINK_PATH . 'Tpl/dispatch_jump.tpl', //默认成功跳转对应的模板文件 'TMPL_ACTION_SUCCESS' => THINK_PATH . 'Tpl/dispatch_jump.t pl', 也可以使用项目内部的模板文件 //默认错误跳转对应的模板文件 'TMPL_ACTION_ERROR' => 'Public:error', //默认成功跳转对应的模板文件 'TMPL_ACTION_SUCCESS' => 'Public:success', 模板文件可以使用模板标签,并且可以使用下面的模板变量: 变量变量 含义含义 $message 页面提示信息 $error 页面错误提示信息 $waitSecond 跳转等待时间 单位为秒 $jumpUrl 跳转页面地址 success和error方法会自动判断当前请求是否属于Ajax请求,如果属于Ajax请 求则会调用ajaxReturn方法返回信息。 ajax方式下面,success和error方法会 封装下面的数据返回: ThinkPHP3.2.3完全开发手册 - 111 -© 本文档使用 看云 构建 $data['info'] = $message; // 提示信息内容 $data['status'] = $status; // 状态 如果是success是1 error 是0 $data['url'] = $jumpUrl; // 成功或者错误的跳转地址 重定向 Controller类的redirect方法可以实现页面的重定向功能。 redirect方法的参数用法和U函数的用法一致(参考URL生成部分),例如: //重定向到New模块的Category操作 $this->redirect('New/category', array('cate_id' => 2), 5, '页面跳转中.. .'); 上面的用法是停留5秒后跳转到New模块的category操作,并且显示页面跳转 中字样,重定向后会改变当前的URL地址。 如果你仅仅是想重定向要一个指定的URL地址,而不是到某个模块的操作方法, 可以直接使用 redirect 函数重定向,例如: //重定向到指定的URL地址 redirect('/New/category/cate_id/2', 5, '页面跳转中...') Redirect函数的第一个参数是一个URL地址。 控制器的redirect方法和redirect函数的区别在于前者是用URL规则定义跳转 地址,后者是一个纯粹的URL地址。 输入变量 ThinkPHP3.2.3完全开发手册 - 112 -© 本文档使用 看云 构建 在Web开发过程中,我们经常需要获取系统变量或者用户提交的数据,这些变 量数据错综复杂,而且一不小心就容易引起安全隐患,但是如果利用好 ThinkPHP提供的变量获取功能,就可以轻松的获取和驾驭变量了。 获取变量 虽然你仍然可以在开发过程中使用传统方式获取各种系统变量,例如: $id = $_GET['id']; // 获取get变量 $name = $_POST['name']; // 获取post变量 $value = $_SESSION['var']; // 获取session变量 $name = $_COOKIE['name']; // 获取cookie变量 $file = $_SERVER['PHP_SELF']; // 获取server变量 但是我们不建议直接使用传统方式获取,因为没有统一的安全处理机制,后期如 果调整的话,改起来会比较麻烦。所以,更好的方式是在框架中统一使用I函数 进行变量获取和过滤。 I方法是ThinkPHP用于更加方便和安全的获取系统输入变量,可以用于任何地 方,用法格式如下: I('变量类型.变量名/修饰符',['默认值'],['过滤方法或正则'],['额外数据源']) 变量类型是指请求方式或者输入类型,包括: 变量类型变量类型 含义含义 get 获取GET参数 post 获取POST参数 param 自动判断请求类型获取GET、POST或者PUT参数 request 获取REQUEST 参数 ThinkPHP3.2.3完全开发手册 - 113 -© 本文档使用 看云 构建 put 获取PUT 参数 session 获取 $_SESSION 参数 cookie 获取 $_COOKIE 参数 server 获取 $_SERVER 参数 globals 获取 $GLOBALS参数 path 获取 PATHINFO模式的URL参数 data 获取 其他类型的参数,需要配合额外数据源参数 变量类型变量类型 含义含义 注意:变量类型不区分大小写,变量名则严格区分大小写。 默认值和过滤方法均属于可选参数。 我们以GET变量类型为例,说明下I方法的使用: echo I('get.id'); // 相当于 $_GET['id'] echo I('get.name'); // 相当于 $_GET['name'] 支持默认值: echo I('get.id',0); // 如果不存在$_GET['id'] 则返回0 echo I('get.name',''); // 如果不存在$_GET['name'] 则返回空字符串 采用方法过滤: // 采用htmlspecialchars方法对$_GET['name'] 进行过滤,如果不存在则 返回空字符串 echo I('get.name','','htmlspecialchars'); 支持直接获取整个变量类型,例如: ThinkPHP3.2.3完全开发手册 - 114 -© 本文档使用 看云 构建 // 获取整个$_GET 数组 I('get.'); 用同样的方式,我们可以获取post或者其他输入类型的变量,例如: I('post.name','','htmlspecialchars'); // 采用htmlspecialchars方法对$_P OST['name'] 进行过滤,如果不存在则返回空字符串 I('session.user_id',0); // 获取$_SESSION['user_id'] 如果不存在则默认为 0 I('cookie.'); // 获取整个 $_COOKIE 数组 I('server.REQUEST_METHOD'); // 获取 $_SERVER['REQUEST_METH OD'] param变量类型是框架特有的支持自动判断当前请求类型的变量获取方式,例 如: echo I('param.id'); 如果当前请求类型是GET,那么等效于 $_GET['id'],如果当前请求类型是 POST或者PUT,那么相当于获取 $_POST['id'] 或者 PUT参数id。 由于param类型是I函数默认获取的变量类型,因此事实上param变量类型的写 法可以简化为: I('id'); // 等同于 I('param.id') I('name'); // 等同于 I('param.name') path类型变量可以用于获取URL参数(必须是PATHINFO模式参数有效,无论 是GET还是POST方式都有效),例如: 当前访问URL地址是 http://serverName/index.php/New/2013/06/01 那么我们可以通过 ThinkPHP3.2.3完全开发手册 - 115 -© 本文档使用 看云 构建 echo I('path.1'); // 输出2013 echo I('path.2'); // 输出06 echo I('path.3'); // 输出01 data类型变量可以用于获取不支持的变量类型的读取,例如: I('data.file1','','',$_FILES); 变量过滤 如果你没有在调用I函数的时候指定过滤方法的话,系统会采用默认的过滤机制 (由DEFAULT_FILTER配置),事实上,该参数的默认设置是: // 系统默认的变量过滤机制 'DEFAULT_FILTER' => 'htmlspecialchars' 也就说,I方法的所有获取变量如果没有设置过滤方法的话都会进行 htmlspecialchars过滤,那么: // 等同于 htmlspecialchars($_GET['name']) I('get.name'); 同样,该参数也可以设置支持多个过滤,例如: 'DEFAULT_FILTER' => 'strip_tags,htmlspecialchars' 设置后,我们在使用: // 等同于 htmlspecialchars(strip_tags($_GET['name'])) I('get.name'); ThinkPHP3.2.3完全开发手册 - 116 -© 本文档使用 看云 构建 如果我们在使用I方法的时候 指定了过滤方法,那么就会忽略 DEFAULT_FILTER的设置,例如: // 等同于 strip_tags($_GET['name']) echo I('get.name','','strip_tags'); I方法的第三个参数如果传入函数名,则表示调用该函数对变量进行过滤并返回 (在变量是数组的情况下自动使用 array_map 进行过滤处理),否则会调用 PHP内置的 filter_var 方法进行过滤处理,例如: I('post.email','',FILTER_VALIDATE_EMAIL); 表示 会对 $_POST['email'] 进行 格式验证,如果不符合要求的话,返回空字 符串。 (关于更多的验证格式,可以参考 官方手册的 filter_var 用法。) 或者 可以用下面的字符标识方式: I('post.email','','email'); 可以支持的过滤名称必须是 filter_list 方法中的有效值(不同的服务器环境可能 有所不同),可能支持的包括: ThinkPHP3.2.3完全开发手册 - 117 -© 本文档使用 看云 构建 int boolean float validate_regexp validate_url validate_email validate_ip string stripped encoded special_chars unsafe_raw email url number_int number_float magic_quotes callback 还可以支持进行正则匹配过滤,例如: // 采用正则表达式进行变量过滤 I('get.name','','/^[A-Za-z]+$/'); I('get.id',0,'/^\d+$/'); 如果正则匹配不通过的话,则返回默认值。 在有些特殊的情况下,我们不希望进行任何过滤,即使DEFAULT _FILT ERDEFAULT _FILT ER已 经有所设置,可以使用: ThinkPHP3.2.3完全开发手册 - 118 -© 本文档使用 看云 构建 // 下面两种方式都不采用任何过滤方法 I('get.name','',''); I('get.id','',false); 一旦过滤参数设置为空字符串或者false,即表示不再进行任何的过滤。 变量修饰符 最新版本的I函数支持对变量使用修饰符功能,可以更方便的通过类型过滤变 量。 用法如下: I('变量类型.变量名/修饰符') 例如: I('get.id/d'); // 强制变量转换为整型 I('post.name/s'); // 强制转换变量为字符串类型 I('post.ids/a'); // 强制变量转换为数组类型 可以使用的修饰符包括: 修饰符修饰符 作用作用 s 强制转换为字符串类型 d 强制转换为整型类型 b 强制转换为布尔类型 a 强制转换为数组类型 f 强制转换为浮点类型 ThinkPHP3.2.3完全开发手册 - 119 -© 本文档使用 看云 构建 请求类型 判断请求类型 在很多情况下面,我们需要判断当前操作的请求类型是GET 、POST 、PUT或 DELETE,一方面可以针对请求类型作出不同的逻辑处理,另外一方面有些情况 下面需要验证安全性,过滤不安全的请求。 系统内置了一些常量用于判断请求类型,包括: 常量常量 说明说明 IS_GET 判断是否是GET方式提交 IS_POST 判断是否是POST方式提交 IS_PUT 判断是否是PUT方式提交 IS_DELETE 判断是否是DELETE方式提交 IS_AJAX 判断是否是AJAX提交 REQUEST_METHOD 当前提交类型 使用举例如下: ThinkPHP3.2.3完全开发手册 - 120 -© 本文档使用 看云 构建 class UserController extends Controller{ public function update(){ if (IS_POST){ $User = M('User'); $User->create(); $User->save(); $this->success('保存完成'); }else{ $this->error('非法请求'); } } } 个别情况下,你可能需要在表单里面添加一个隐藏域,告诉后台属于ajax方 式提交,默认的隐藏域名称是ajax(可以通过VAR_AJAX_SUBMIT配置), 如果是JQUERY类库的话,则无需添加任何隐藏域即可自动判断。 空操作 空操作是指系统在找不到请求的操作方法的时候,会定位到空操作( _empty ) 方法来执行,利用这个机制,我们可以实现错误页面和一些URL的优化。 例如,下面我们用空操作功能来实现一个城市切换的功能。 我们只需要给 CityController类定义一个 _empty (空操作)方法: ThinkPHP3.2.3完全开发手册 - 121 -© 本文档使用 看云 构建 city($name); } //注意 city方法 本身是 protected 方法 protected function city($name){ //和$name这个城市相关的处理 echo '当前城市' . $name; } } 接下来,我们就可以在浏览器里面输入 http://serverName/index.php/Home/City/beijing/ http://serverName/index.php/Home/City/shanghai/ http://serverName/index.php/Home/City/shenzhen/ 由于City控制器并没有定义beijing、shanghai或者shenzhen操作方法,因此 系统会定位到空操作方法 _empty 中去解析,_empty方法的参数就是当前URL 里面的操作名,因此会看到依次输出的结果是: 当前城市:beijing 当前城市:shanghai 当前城市:shenzhen 注意:空操作方法仅在你的控制器类继承系统的Think\Controller类才有 效,否则需要自己定义 __call 来实现。 ThinkPHP3.2.3完全开发手册 - 122 -© 本文档使用 看云 构建 空控制器 空控制器的概念是指当系统找不到请求的控制器名称的时候,系统会尝试定位空 控制器(EmptyController),利用这个机制我们可以用来定制错误页面和进行 URL的优化。 现在我们把前面的需求进一步,把URL由原来的 http://serverName/index.php/Home/City/shanghai/ 变成 http://serverName/index.php/Home/shanghai/ 这样更加简单的方式,如果按照传统的模式,我们必须给每个城市定义一个控制 器类,然后在每个控制器类的index方法里面进行处理。可是如果使用空控制器 功能,这个问题就可以迎刃而解了。 我们可以给项目定义一个EmptyController类 ThinkPHP3.2.3完全开发手册 - 123 -© 本文档使用 看云 构建 city($cityName); } //注意 city方法 本身是 protected 方法 protected function city($name){ //和$name这个城市相关的处理 echo '当前城市' . $name; } } 接下来,我们就可以在浏览器里面输入 http://serverName/index.php/Home/beijing/ http://serverName/index.php/Home/shanghai/ http://serverName/index.php/Home/shenzhen/ 由于系统并不存在beijing、shanghai或者shenzhen控制器,因此会定位到空 控制器(EmptyController)去执行,会看到依次输出的结果是: 当前城市:beijing 当前城市:shanghai 当前城市:shenzhen 空控制器和空操作还可以同时使用,用以完成更加复杂的操作。 ThinkPHP3.2.3完全开发手册 - 124 -© 本文档使用 看云 构建 插件控制器 插件控制器可以更加方便的在控制器以外扩展你的功能,当URL中传入插件控制 器变量的时候,会自动定位到插件控制器中的操作方法。 插件控制器的变量由参数 VAR_ADDONVAR_ADDON 进行设置,默认为addon,例如我们 在URL中传入: http://serverName/Home/info/index/addon/SystemInfo 由于传入了addon参数,因此这里的Info控制器并非原来的 Home/Controller/InfoController.class.php 而是调用SystemInfo插件的InfoController控制器了,文件位于 Addon/SystemInfo/Controller/InfoController.class.php 。 插件控制器本身的定义和普通的访问控制器一样,例如: namespace Addon\SystemInfo\Controller; class InfoController extends \Think\Controller{ public function index(){ echo 'Addon SystemInfo'; } } 这样,我们在访问 http://serverName/Home/info/index/addon/SystemInfo 的时候 就会输 出 Addon SystemInfo 如果我们的插件目录不是Addon,而是Plugin,那么需要在配置文件中定义: 'VAR_ADDON' => 'plugin' ThinkPHP3.2.3完全开发手册 - 125 -© 本文档使用 看云 构建 然后访问URL地址就变成了 http://serverName/Home/info/index/plugin/SystemInfo 操作绑定到类 定义 ThinkPHP3.2版本提供了把每个操作方法定位到一个类的功能,可以让你的开 发工作更细化,可以设置参数ACT ION_BIND_CLASSACT ION_BIND_CLASS,例如: 'ACTION_BIND_CLASS' => True, 设置后,我们的控制器定义有所改变,以URL访问为 http://serverName/Home/Index/index 为例,原来的控制器文件定义位置 为: Application/Home/Controller/IndexController.class.php 控制器类的定义如下: namespace Home\Controller; use Think\Controller; class IndexController extends Controller{ public function index(){ echo '执行Index控制器的index操作'; } } 可以看到,实际上我们调用的是 Home\Controller\IndexController 类 的indexindex方法。 ThinkPHP3.2.3完全开发手册 - 126 -© 本文档使用 看云 构建 设置后,控制器文件位置改为: Application/Home/Controller/Index/index.class.php 控制器类的定义如下: namespace Home\Controller\Index; use Think\Controller; class index extends Controller{ public function run(){ echo '执行Index控制器的index操作'; } } 现在,我们调用的其实是 Home\Controller\Index\index 类的runrun方法。 run方法依旧可以支持传入参数和进行Action参数绑定操作,但不再支持A方 法实例化和R方法远程调用,我们建议R方法不要进行当前访问控制器的远程 调用。 前置和后置操作 当设置操作方法绑定到类后,前置和后置操作的定义有所改变,只需要在类里面 定义 _before_run 和 _after_run 方法即可,例如: ThinkPHP3.2.3完全开发手册 - 127 -© 本文档使用 看云 构建 namespace Home\Controller\Index; use Think\Controller; class index extends Controller{ public function _before_run(){ echo 'before_'.ACTION_NAME; } public function run(){ echo '执行Index控制器的index操作'; } public function _after_run(){ echo 'after_'.ACTION_NAME; } } 空控制器 操作方法绑定到类后,一样可以支持空控制器,我们可以创建 Application/Home/Controller/_empty 目录,即表示如果找不到当前的控制 器的话,会到_empty控制器目录下面定位操作方法。 例如,我们访问了URL地址 http://serverName/Home/Test/index ,但并不 存在 Application/Home/Controller/Test 目录,但是有定义 Application/Home/Controller/_empty 目录。 并且我们有定义: Application/Home/Controller/_empty/index.class.php 控制器定义如下: ThinkPHP3.2.3完全开发手册 - 128 -© 本文档使用 看云 构建 namespace Home\Controller\_empty; use Think\Controller; class index extends Controller{ public function run(){ echo '执行'CONTROLLER_NAME.'控制器的'.ACTION_NAME.'操 作'; } } 访问 http://serverName/Home/Test/index 后 输出结果显示: 执行Test控制器的index操作 空操作 操作绑定到类后,我们依然可以实现空操作方法,我们只要定义一个 Home\Controller\Index\_empty 类,就可以支持Index控制器的空操作访 问,例如: 控制器定义如下: namespace Home\Controller\Index; use Think\Controller; class _empty extends Controller{ public function run(){ echo '执行Index控制器的'.ACTION_NAME.'操作'; } } 当我们访问 http://serverName/Home/Index/test 后 输出结果显示: 执行Index控制器的test操作 ThinkPHP3.2.3完全开发手册 - 129 -© 本文档使用 看云 构建 模型 在ThinkPHP中基础的模型类就是 Think\Model 类,该类完成了基本的 CURD、ActiveRecord模式、连贯操作和统计查询,一些高级特性被封装到另 外的模型扩展中。 基础模型类的设计非常灵活,甚至可以无需进行任何模型定义,就可以进行相关 数据表的ORM和CURD操作,只有在需要封装单独的业务逻辑的时候,模型类 才是必须被定义的。 模型定义 模型定义 模型类并非必须定义,只有当存在独立的业务逻辑或者属性的时候才需要定 义。 模型类通常需要继承系统的\Think\Model类或其子类,下面是一个 Home\Model\UserModel类的定义: namespace Home\Model; use Think\Model; class UserModel extends Model { } 模型类的作用大多数情况是操作数据表的,如果按照系统的规范来命名模型类的 话,大多数情况下是可以自动对应数据表。 模型类的命名规则是除去表前缀的数据表名称,采用驼峰法命名,并且首字母大 ThinkPHP3.2.3完全开发手册 - 130 -© 本文档使用 看云 构建 写,然后加上模型层的名称(默认定义是Model),例如: 模型名模型名 约定对应数据表(假设数据库的前缀定义是约定对应数据表(假设数据库的前缀定义是 think_)think_) UserModel think_user UserTypeModel think_user_type 如果你的规则和上面的系统约定不符合,那么需要设置Model类的数据表名称 属性,以确保能够找到对应的数据表。 数据表定义 在ThinkPHP的模型里面,有几个关于数据表名称的属性定义: 属性属性 说明说明 tablePrefix 定义模型对应数据表的前缀,如果未定义则获取配置文 件中的DB_PREFIX参数 tableName 不包含表前缀的数据表名称,一般情况下默认和模型名 称相同,只有当你的表名和当前的模型类的名称不同的 时候才需要定义。 trueTableName 包含前缀的数据表名称,也就是数据库中的实际表名, 该名称无需设置,只有当上面的规则都不适用的情况或 者特殊情况下才需要设置。 dbName 定义模型当前对应的数据库名称,只有当你当前的模型 类对应的数据库名称和配置文件不同的时候才需要定 义。 举个例子来加深理解,例如,在数据库里面有一个 think_categories 表,而我 们定义的模型类名称是 CategoryModel ,按照系统的约定,这个模型的名称 是Category,对应的数据表名称应该是 think_category (全部小写),但是 ThinkPHP3.2.3完全开发手册 - 131 -© 本文档使用 看云 构建 现在的数据表名称是 think_categories ,因此我们就需要设置 tableName 属 性来改变默认的规则(假设我们已经在配置文件里面定义了 DB_PREFIX 为 think_)。 namespace Home\Model; use Think\Model; class CategoryModel extends Model { protected $tableName = 'categories'; } 注意这个属性的定义不需要加表的前缀 think_ 如果我们需要CategoryModel模型对应操作的数据表是 top_category ,那 么我们只需要设置数据表前缀即可: namespace Home\Model; use Think\Model; class CategoryModel extends Model { protected $tablePrefix = 'top_'; } 如果你的数据表直接就是 category ,而没有前缀,则可以设置 tablePrefix 为 空字符串。 namespace Home\Model; use Think\Model; class CategoryModel extends Model { protected $tablePrefix = ''; } 没有表前缀的情况必须设置,否则会获取当前配置文件中的 DB_PREFIX 。 ThinkPHP3.2.3完全开发手册 - 132 -© 本文档使用 看云 构建 而对于另外一种特殊情况,我们需要操作的数据表是 top_categories ,这个时 候我们就需要定义 trueTableName 属性 namespace Home\Model; use Think\Model; class CategoryModel extends Model { protected $trueTableName = 'top_categories'; } 注意 trueTableName 需要完整的表名定义。 除了数据表的定义外,还可以对数据库进行定义(用于操作当前数据库以外的数 据表),例如 top.top_categories : namespace Home\Model; use Think\Model; class CategoryModel extends Model { protected $trueTableName = 'top_categories'; protected $dbName = 'top'; } 系统的规则下,tableName会转换为小写定义,但是trueTableName定义 的数据表名称是保持原样。因此,如果你的数据表名称需要区分大小写的情 况,那么可以通过设置trueTableName定义来解决。 模型实例化 在ThinkPHP中,可以无需进行任何模型定义。只有在需要封装单独的业务逻辑 的时候,模型类才是必须被定义的,因此ThinkPHP在模型上有很多的灵活和方 ThinkPHP3.2.3完全开发手册 - 133 -© 本文档使用 看云 构建 便性,让你无需因为表太多而烦恼。 根据不同的模型定义,我们有几种实例化模型的方法,根据需要采用不同的方 式: 直接实例化 可以和实例化其他类库一样实例化模型类,例如: $User = new \Home\Model\UserModel(); $Info = new \Admin\Model\InfoModel(); // 带参数实例化 $New = new \Home\Model\NewModel('blog','think_',$connection ); 模型类通常都是继承系统的\Think\Model类,该类的架构方法有三个参数,分 别是: Model(['模型名'],['数据表前缀'],['数据库连接信息']); 三个参数都是可选的,大多数情况下,我们根本无需传入任何参数即可实例化。 参数参数 描述描述 模型名 模型的名称 和数据表前缀一起配合用于自动识别数据表 名称 数据表前缀 当前数据表前缀 和模型名一起配合用于自动识别数据表 名称 数据库连接信 息 当前数据表的数据库连接信息 如果没有则获取配置文件 中的 数据表前缀传入空字符串表示取当前配置的表前缀,如果当前数据表没有前 缀,则传入null即可。 ThinkPHP3.2.3完全开发手册 - 134 -© 本文档使用 看云 构建 数据库连接信息参数支持三种格式: 1、字符串定义 字符串定义采用DSN格式定义,格式定义规范为: 数据库类型://用户名:密码@数据库主机名或者IP:数据库端口/数据库名#字符集 例如: new \Home\Model\NewModel('blog','think_','mysql://root:1234@l ocalhost/demo'); 2、数组定义 可以传入数组格式的数据库连接信息,例如: $connection = array( 'db_type' => 'mysql', 'db_host' => '127.0.0.1', 'db_user' => 'root', 'db_pwd' => '12345', 'db_port' => 3306, 'db_name' => 'demo', 'db_charset' => 'utf8', ); new \Home\Model\NewModel('new','think_',$connection); 如果需要的话,还可以传入更多的连接参数,包括数据的部署模式和调试模式设 定,例如: ThinkPHP3.2.3完全开发手册 - 135 -© 本文档使用 看云 构建 $connection = array( 'db_type' => 'mysql', 'db_host' => '192.168.1.2,192.168.1.3', 'db_user' => 'root', 'db_pwd' => '12345', 'db_port' => 3306, 'db_name' => 'demo', 'db_charset' => 'utf8', 'db_deploy_type'=> 1, 'db_rw_separate'=> true, 'db_debug' => true, ); // 分布式数据库部署 并且采用读写分离 开启数据库调试模式 new \Home\Model\NewModel('new','think_',$connection); 注意,如果设置了db_debug参数,那么数据库调试模式就不再受 APP_DEBUG常量影响。 3、配置定义 我们可以事先在配置文件中定义好数据库连接信息,然后在实例化的时候直接传 入配置的名称即可,例如: ThinkPHP3.2.3完全开发手册 - 136 -© 本文档使用 看云 构建 //数据库配置1 'DB_CONFIG1' => array( 'db_type' => 'mysql', 'db_user' => 'root', 'db_pwd' => '1234', 'db_host' => 'localhost', 'db_port' => '3306', 'db_name' => 'thinkphp' ), //数据库配置2 'DB_CONFIG2' => 'mysql://root:1234@localhost:3306/thinkphp', 在配置文件中定义数据库连接信息的时候也支持字符串和数组格式,格式和上面 实例化传入的参数一样。 然后,我们就可以这样实例化模型类传入连接信息: new \Home\Model\NewModel('new','think_','DB_CONFIG1'); new \Home\Model\BlogModel('blog','think_','DB_CONFIG2'); 事实上,当我们实例化的时候没有传入任何的数据库连接信息的时候,系统其实 默认会获取配置文件中的相关配置参数,包括: 'DB_TYPE' => '', // 数据库类型 'DB_HOST' => '', // 服务器地址 'DB_NAME' => '', // 数据库名 'DB_USER' => '', // 用户名 'DB_PWD' => '', // 密码 'DB_PORT' => '', // 端口 'DB_PREFIX' => '', // 数据库表前缀 'DB_DSN' => '', // 数据库连接DSN 用于PDO方式 'DB_CHARSET' => 'utf8', // 数据库的编码 默认为utf8 ThinkPHP3.2.3完全开发手册 - 137 -© 本文档使用 看云 构建 如果应用配置文件中有配置上述数据库连接信息的话,实例化模型将会变得非常 简单。 D方法实例化 上面实例化的时候我们需要传入完整的类名,系统提供了一个快捷方法D用于数 据模型的实例化操作。 要实例化自定义模型类,可以使用下面的方式: select(); 当 \Home\Model\UserModel 类不存在的时候,D函数会尝试实例化公 共模块下面的 \Common\Model\UserModel 类。 D方法的参数就是模型的名称,并且和模型类的大小写定义是一致的,例如: 参数参数 实例化的模型文件(假设当前模块为Home)实例化的模型文件(假设当前模块为Home) User 对应的模型类文件的 \Home\Model\UserModel.class.php UserType 对应的模型类文件的 \Home\Model\UserTypeModel.class.php 如果在Linux环境下面,一定要注意D方法实例化的时候的模型名称的大小 写。 D方法可以自动检测模型类,如果存在自定义的模型类,则实例化自定义模型 类,如果不存在,则会实例化系统的\Think\Model基类,同时对于已实例化过 ThinkPHP3.2.3完全开发手册 - 138 -© 本文档使用 看云 构建 的模型,不会重复实例化。 D方法还可以支持跨模块调用,需要使用: //实例化Admin模块的User模型 D('Admin/User'); //实例化Extend扩展命名空间下的Info模型 D('Extend://Editor/Info'); 注意:跨模块实例化模型类的时候 不支持自动加载公共模块的模型类。 M方法实例化模型 D方法实例化模型类的时候通常是实例化某个具体的模型类,如果你仅仅是对数 据表进行基本的CURD操作的话,使用M方法实例化的话,由于不需要加载具体 的模型类,所以性能会更高。 例如: // 使用M方法实例化 $User = M('User'); // 和用法 $User = new \Think\Model('User'); 等效 // 执行其他的数据操作 $User->select(); M方法也可以支持跨库操作,例如: // 使用M方法实例化 操作db_name数据库的ot_user表 $User = M('db_name.User','ot_'); // 执行其他的数据操作 $User->select(); M方法的参数和\Think\Model类的参数是一样的,也就是说,我们也可以这样 实例化: ThinkPHP3.2.3完全开发手册 - 139 -© 本文档使用 看云 构建 $New = M('new','think_',$connection); // 等效于 $New = new \Think\Model('new','think_',$connection); 具体的参数含义可以参考前面的介绍。 M方法实例化的时候,默认情况下是直接实例化系统的\Think\Model类,如果 我们希望实例化其他的公共模型类的话,可以使用如下方法: $User = M('\Home\Model\CommonModel:User','think_','db_config' ); // 相当于 $User = new \Home\Model\CommonModel('User','think_', 'db_config'); 如果你的模型类有自己的业务逻辑,M方法是无法支持的,就算是你已经定 义了具体的模型类,M方法实例化的时候是会直接忽略。 实例化空模型类 如果你仅仅是使用原生SQL查询的话,不需要使用额外的模型类,实例化一个 空模型类即可进行操作了,例如: //实例化空模型 $Model = new Model(); //或者使用M快捷方法是等效的 $Model = M(); //进行原生的SQL查询 $Model->query('SELECT * FROM think_user WHERE status = 1'); 实例化空模型类后还可以用table方法切换到具体的数据表进行操作 我们在实例化的过程中,经常使用D方法和M方法,这两个方法的区别在于M方 ThinkPHP3.2.3完全开发手册 - 140 -© 本文档使用 看云 构建 法实例化模型无需用户为每个数据表定义模型类,如果D方法没有找到定义的模 型类,则会自动调用M方法。 字段定义 通常每个模型类是操作某个数据表,在大多数情况下,系统会自动获取当前数据 表的字段信息。 系统会在模型首次实例化的时候自动获取数据表的字段信息(而且只需要一次, 以后会永久缓存字段信息,除非设置不缓存或者删除),如果是调试模式则不会 生成字段缓存文件,则表示每次都会重新获取数据表字段信息。 字段缓存保存在 Runtime/Data/_fields/ 目录下面,缓存机制是每个模型对应 一个字段缓存文件(注意:并非每个数据表对应一个字段缓存文件),命名格式 是: 数据库名.数据表前缀+模型名(小写).php 例如: demo.think_user.php // User模型生成的字段缓存文件 demo.top_article.php // Article模型生成的字段缓存文件 字段缓存包括数据表的字段信息、主键字段和是否自动增长,如果开启字段类型 验证的话还包括字段类型信息等等,无论是用M方法还是D方法,或者用原生的 实例化模型类一般情况下只要是不开启调试模式都会生成字段缓存(字段缓存可 以单独设置关闭)。 可以通过设置 DB_FIELDS_CACHE 参数来关闭字段自动缓存,如果在开发的 时候经常变动数据库的结构,而不希望进行数据表的字段缓存,可以在项目配置 文件中增加如下配置: ThinkPHP3.2.3完全开发手册 - 141 -© 本文档使用 看云 构建 // 关闭字段缓存 'DB_FIELDS_CACHE'=>false 注意:调试模式下面由于考虑到数据结构可能会经常变动,所以默认是关闭 字段缓存的。 如果需要显式获取当前数据表的字段信息,可以使用模型类的getDbFields方法 来获取当前数据对象的全部字段信息,例如: $User = M('User'); $fields = $User->getDbFields(); 如果你在部署模式下面修改了数据表的字段信息,可能需要清空 Data/_fields 目录下面的缓存文件,让系统重新获取更新的数据表字段信息,否则会发生新增 的字段无法写入数据库的问题。 如果不希望依赖字段缓存或者想提高性能,也可以在模型类里面手动定义数据表 字段的名称,可以避免IO加载的效率开销,例如: namespace Home\Model; use Think\Model; class UserModel extends Model { protected $fields = array('id', 'username', 'email', 'age'); protected $pk = 'id'; } pk 属性定义当前数据表的主键名,默认值就是id,因此如果是id的话可以无需 定义。 如果你的数据表使用了复合主键,可以这样定义: ThinkPHP3.2.3完全开发手册 - 142 -© 本文档使用 看云 构建 namespace Home\Model; use Think\Model; class ScoreModel extends Model { protected $fields = array('user_id', 'lession_id','score'); protected $pk = array('user_id','lession_id'); } 除了可以设置数据表的字段之外,我们还可以定义字段的类型,用于某些验证环 节。例如: namespace Home\Model; use Think\Model; class UserModel extends Model { protected $fields = array('id', 'username', 'email', 'age', '_type'=>array('id'=>'bigint','username'=>'varchar','email'=>'va rchar','age'=>'int') ); } 连接数据库 ThinkPHP内置了抽象数据库访问层,把不同的数据库操作封装起来,我们只需 要使用公共的Db类进行操作,而无需针对不同的数据库写不同的代码和底层实 现,Db类会自动调用相应的数据库驱动来处理。目前包含了Mysql、 SqlServer、PgSQL、Sqlite、Oracle、Ibase、Mongo等数据库的支持,并 且采用PDO方式。 如果应用需要使用数据库,必须配置数据库连接信息,数据库的配置文件有多种 定义方式。 ThinkPHP3.2.3完全开发手册 - 143 -© 本文档使用 看云 构建 一、全局配置定义 常用的配置方式是在应用配置文件或者模块配置文件中添加下面的配置参数: //数据库配置信息 'DB_TYPE' => 'mysql', // 数据库类型 'DB_HOST' => '127.0.0.1', // 服务器地址 'DB_NAME' => 'thinkphp', // 数据库名 'DB_USER' => 'root', // 用户名 'DB_PWD' => '123456', // 密码 'DB_PORT' => 3306, // 端口 'DB_PREFIX' => 'think_', // 数据库表前缀 'DB_CHARSET'=> 'utf8', // 字符集 'DB_DEBUG' => TRUE, // 数据库调试模式 开启后可以记录SQL日志 数据库的类型由DB_T YPEDB_T YPE 参数设置。 配置文件定义的数据库连接信息一般是系统默认采用的,因为一般一个应用的数 据库访问配置是相同的。该方法系统在连接数据库的时候会自动获取,无需手动 连接。 可以对每个模块定义不同的数据库连接信息,如果开启了调试模式的话,还可以 在不同的应用状态的配置文件里面定义独立的数据库配置信息。 二、模型类定义 如果在某个模型类里面定义了 connection 属性的话,则实例化该自定义模型 的时候会采用定义的数据库连接信息,而不是配置文件中设置的默认连接信息, 通常用于某些数据表位于当前数据库连接之外的其它数据库,例如: ThinkPHP3.2.3完全开发手册 - 144 -© 本文档使用 看云 构建 //在模型里单独设置数据库连接信息 namespace Home\Model; use Think\Model; class UserModel extends Model{ protected $connection = array( 'db_type' => 'mysql', 'db_user' => 'root', 'db_pwd' => '1234', 'db_host' => 'localhost', 'db_port' => '3306', 'db_name' => 'thinkphp', 'db_charset' => 'utf8', ); } 也可以采用字符串方式定义,定义格式为: 数据库类型://用户名:密码@数据库地址:数据库端口/数据库名#字符集 例如: //在模型里单独设置数据库连接信息 namespace Home\Model; use Think\Model; class UserModel extends Model{ //或者使用字符串定义 protected $connection = 'mysql://root:1234@localhost:3306/thi nkphp#utf8'; } 如果我们已经在配置文件中配置了额外的数据库连接信息,例如: ThinkPHP3.2.3完全开发手册 - 145 -© 本文档使用 看云 构建 //数据库配置1 'DB_CONFIG1' => array( 'db_type' => 'mysql', 'db_user' => 'root', 'db_pwd' => '1234', 'db_host' => 'localhost', 'db_port' => '3306', 'db_name' => 'thinkphp', 'db_charset'=> 'utf8', ), //数据库配置2 'DB_CONFIG2' => 'mysql://root:1234@localhost:3306/thinkphp#ut f8'; 那么,我们可以把模型类的属性定义改为: //在模型里单独设置数据库连接信息 namespace Home\Model; use Think\Model; class UserModel extends Model{ //调用配置文件中的数据库配置1 protected $connection = 'DB_CONFIG1'; } //在模型里单独设置数据库连接信息 namespace Home\Model; use Think\Model; class InfoModel extends Model{ //调用配置文件中的数据库配置1 protected $connection = 'DB_CONFIG2'; } ThinkPHP3.2.3完全开发手册 - 146 -© 本文档使用 看云 构建 三、实例化定义 除了在模型定义的时候指定数据库连接信息外,我们还可以在实例化的时候指定 数据库连接信息,例如: 如果采用的是M方法实例化模型的话,也可以支持传 入不同的数据库连接信息,例如: $User = M('User','other_','mysql://root:1234@localhost/demo#utf8' ); 表示实例化User模型,连接的是demo数据库的other_user表,采用的连接信 息是第三个参数配置的。如果我们在项目配置文件中已经配置了 DB_CONFIG2 的话,也可以采用: $User = M('User','other_','DB_CONFIG2'); 需要注意的是,ThinkPHP的数据库连接是惰性的,所以并不是在实例化的 时候就连接数据库,而是在有实际的数据操作的时候才会去连接数据库(额 外的情况是,在系统第一次实例化模型的时候,会自动连接数据库获取相关 模型类对应的数据表的字段信息)。 切换数据库 除了在预先定义数据库连接和实例化的时候指定数据库连接外,我们还可以在模 型操作过程中动态的切换数据库,支持切换到相同和不同的数据库类型。用法很 简单, 只需要调用Model类的db方法,用法: Model->db("数据库编号","数据库配置"); 数据库编号用数字格式,对于已经调用过的数据库连接,是不需要再传入数据库 ThinkPHP3.2.3完全开发手册 - 147 -© 本文档使用 看云 构建 连接信息的,系统会自动记录。对于默认的数据库连接,内部的数据库编号是 0,因此为了避免冲突,请不要再次定义数据库编号为0的数据库配置。 数据库配置的定义方式和模型定义connection属性一样,支持数组、字符串以 及调用配置参数三种格式。 Db方法调用后返回当前的模型实例,直接可以继续进行模型的其他操作,所以 该方法可以在查询的过程中动态切换,例如: $this->db(1,"mysql://root:123456@localhost:3306/test")->query(" 查询SQL"); 该方法添加了一个编号为1的数据库连接,并自动切换到当前的数据库连接。 当第二次切换到相同的数据库的时候,就不需要传入数据库连接信息了,可以直 接使用: $this->db(1)->query("查询SQL"); 如果需要切换到默认的数据库连接,只需要调用: $this->db(0); 如果我们已经在项目配置中定义了其他的数据库连接信息,例如: ThinkPHP3.2.3完全开发手册 - 148 -© 本文档使用 看云 构建 //数据库配置1 'DB_CONFIG1' = array( 'db_type' => 'mysql', 'db_user' => 'root', 'db_pwd' => '1234', 'db_host' => 'localhost', 'db_port' => '3306', 'db_name' => 'thinkphp' ), //数据库配置2 'DB_CONFIG2' => 'mysql://root:1234@localhost:3306/thinkphp'; 我们就可以直接在db方法中调用配置进行连接了: $this->db(1,"DB_CONFIG1")->query("查询SQL"); $this->db(2,"DB_CONFIG2")->query("查询SQL"); 如果切换数据库之后,数据表和当前不一致的话,可以使用table方法指定要操 作的数据表: $this->db(1)->table("top_user")->find(); 分布式数据库支持 ThinkPHP内置了分布式数据库的支持,包括主从式数据库的读写分离,但是分 布式数据库必须是相同的数据库类型。 配置 DB_DEPLOY_TYPE 为1 可以采用分布式数据库支持。如果采用分布式数 据库,定义数据库配置信息的方式如下: ThinkPHP3.2.3完全开发手册 - 149 -© 本文档使用 看云 构建 //分布式数据库配置定义 'DB_DEPLOY_TYPE'=> 1, // 设置分布式数据库支持 'DB_TYPE' => 'mysql', //分布式数据库类型必须相同 'DB_HOST' => '192.168.0.1,192.168.0.2', 'DB_NAME' => 'thinkphp', //如果相同可以不用定义多个 'DB_USER' => 'user1,user2', 'DB_PWD' => 'pwd1,pwd2', 'DB_PORT' => '3306', 'DB_PREFIX' => 'think_', 连接的数据库个数取决于DB_HOST定义的数量,所以即使是两个相同的IP也需 要重复定义,但是其他的参数如果存在相同的可以不用重复定义,例如: 'DB_PORT'=>'3306,3306' 和 'DB_PORT'=>'3306' 等效。 'DB_USER'=>'user1', 'DB_PWD'=>'pwd1', 和 'DB_USER'=>'user1,user1', 'DB_PWD'=>'pwd1,pwd1', 等效。 ThinkPHP3.2.3完全开发手册 - 150 -© 本文档使用 看云 构建 还可以设置分布式数据库的读写是否分离,默认的情况下读写不分离,也就是每 台服务器都可以进行读写操作,对于主从式数据库而言,需要设置读写分离,通 过下面的设置就可以: 'DB_RW_SEPARATE'=>true, 在读写分离的情况下,默认第一个数据库配置是主服务器的配置信息,负责写入 数据,如果设置了 DB_MASTER_NUM 参数,则可以支持多个主服务器写入。 其它的都是从数据库的配置信息,负责读取数据,数量不限制。每次连接从服务 器并且进行读取操作的时候,系统会随机进行在从服务器中选择。 还可以设置 DB_SLAVE_NO 指定某个服务器进行读操作。 3.2.3版本开始,如果从数据库连接错误,会自动切换到主数据库连接。 调用模型的CURD操作的话,系统会自动判断当前执行的方法的读操作还是写操 作,如果你用的是原生SQL,那么需要注意系统的默认规则: 写操作必须用模写操作必须用模 型的execute方法,读操作必须用模型的query方法型的execute方法,读操作必须用模型的query方法,否则会发生主从读写 错乱的情况。 注意:主从数据库的数据同步工作不在框架实现,需要数据库考虑自身的同 步或者复制机制。 连贯操作 ThinkPHP模型基础类提供的连贯操作方法(也有些框架称之为链式操作),可 以有效的提高数据存取的代码清晰度和开发效率,并且支持所有的CURD操作。 使用也比较简单, 假如我们现在要查询一个User表的满足状态为1的前10条记 录,并希望按照用户的创建时间排序 ,代码如下: ThinkPHP3.2.3完全开发手册 - 151 -© 本文档使用 看云 构建 $User->where('status=1')->order('create_time')->limit(10)->select( ); 这里的 where 、 order 和 limit 方法就被称之为连贯操作方法,除了select方 法必须放到最后一个外(因为select方法并不是连贯操作方法),连贯操作的方 法调用顺序没有先后,例如,下面的代码和上面的等效: $User->order('create_time')->limit(10)->where('status=1')->select( ); 如果不习惯使用连贯操作的话,还支持直接使用参数进行查询的方式。例如上面 的代码可以改写为: $User->select(array('order'=>'create_time','where'=>'status=1','limi t'=>'10')); 使用数组参数方式的话,索引的名称就是连贯操作的方法名称。其实不仅仅是查 询方法可以使用连贯操作,包括所有的CURD方法都可以使用,例如: $User->where('id=1')->field('id,name,email')->find(); $User->where('status=1 and id=1')->delete(); 连贯操作通常只有一个参数,并且仅在当此查询或者操作有效,完成后会自动清 空连贯操作的所有传值(有个别特殊的连贯操作有多个参数,并且会记录当前的 传值)。简而言之,连贯操作的结果不会带入以后的查询。 系统支持的连贯操作方法有: 连贯操作连贯操作 作用作用 支持的参数类型支持的参数类型 where* 用于查询或者更新条件的定义 字符串、数组和对 象 ThinkPHP3.2.3完全开发手册 - 152 -© 本文档使用 看云 构建 table 用于定义要操作的数据表名称 字符串和数组 alias 用于给当前数据表定义别名 字符串 data 用于新增或者更新数据之前的数据对象 赋值 数组和对象 field 用于定义要查询的字段(支持字段排 除) 字符串和数组 order 用于对结果排序 字符串和数组 limit 用于限制查询结果数量 字符串和数字 page 用于查询分页(内部会转换成limit) 字符串和数字 group 用于对查询的group支持 字符串 having 用于对查询的having支持 字符串 join* 用于对查询的join支持 字符串和数组 union* 用于对查询的union支持 字符串、数组和对 象 distinct 用于查询的distinct支持 布尔值 lock 用于数据库的锁机制 布尔值 cache 用于查询缓存 支持多个参数 relation 用于关联查询(需要关联模型支持) 字符串 result 用于返回数据转换 字符串 validate 用于数据自动验证 数组 auto 用于数据自动完成 数组 filter 用于数据过滤 字符串 scope* 用于命名范围 字符串、数组 bind* 用于数据绑定操作 数组或多个参数 连贯操作连贯操作 作用作用 支持的参数类型支持的参数类型 ThinkPHP3.2.3完全开发手册 - 153 -© 本文档使用 看云 构建 token 用于令牌验证 布尔值 comment 用于SQL注释 字符串 index 用于数据集的强制索引(3.2.3新增) 字符串 strict 用于数据入库的严格检测(3.2.3新增) 布尔值 连贯操作连贯操作 作用作用 支持的参数类型支持的参数类型 所有的连贯操作都返回当前的模型实例对象(this),其中带*标识的表示支 持多次调用。 WHERE where方法的用法是ThinkPHP查询语言的精髓,也是ThinkPHP ORM的重要 组成部分和亮点所在,可以完成包括普通查询、表达式查询、快捷查询、区间查 询、组合查询在内的查询操作。where方法的参数支持字符串和数组,虽然也 可以使用对象但并不建议。 字符串条件 使用字符串条件直接查询和操作,例如: $User = M("User"); // 实例化User对象 $User->where('type=1 AND status=1')->select(); 最后生成的SQL语句是 SELECT * FROM think_user WHERE type=1 AND status=1 使用字符串条件的时候,建议配合预处理机制,确保更加安全,例如: ThinkPHP3.2.3完全开发手册 - 154 -© 本文档使用 看云 构建 $Model->where("id=%d and username='%s' and xx='%f'",array($id, $username,$xx))->select(); 或者使用: $Model->where("id=%d and username='%s' and xx='%f'",$id,$user name,$xx)->select(); 如果 $id 变量来自用户提交或者URL地址的话,如果传入的是非数字类型,则会 强制格式化为数字格式后进行查询操作。 字符串预处理格式类型支持指定数字、字符串等,具体可以参考vsprintf方法的 参数说明。 数组条件 数组条件的where用法是ThinkPHP推荐的用法。 普通查询 最简单的数组查询方式如下: $User = M("User"); // 实例化User对象 $map['name'] = 'thinkphp'; $map['status'] = 1; // 把查询条件传入查询方法 $User->where($map)->select(); 最后生成的SQL语句是 SELECT * FROM think_user WHERE `name`='thinkphp' AND status =1 ThinkPHP3.2.3完全开发手册 - 155 -© 本文档使用 看云 构建 表达式查询 上面的查询条件仅仅是一个简单的相等判断,可以使用查询表达式支持更多的 SQL查询语法,查询表达式的使用格式: $map['字段1'] = array('表达式','查询条件1'); $map['字段2'] = array('表达式','查询条件2'); $Model->where($map)->select(); // 也支持 表达式不分大小写,支持的查询表达式有下面几种,分别表示的含义是: 表达式表达式 含义含义 EQ 等于(=) NEQ 不等于(<>) GT 大于(>) EGT 大于等于(>=) LT 小于(<) ELT 小于等于(<=) LIKE 模糊查询 [NOT] BETWEEN (不在)区间查询 [NOT] IN (不在)IN 查询 EXP 表达式查询,支持SQL语法 多次调用 where方法支持多次调用,但字符串条件只能出现一次,例如: ThinkPHP3.2.3完全开发手册 - 156 -© 本文档使用 看云 构建 $map['a'] = array('gt',1); $where['b'] = 1; $Model->where($map)->where($where)->where('status=1')->sele ct(); 多次的数组条件表达式会最终合并,但字符串条件则只支持一次。 更多的查询用法,可以参考查询语言部分。 TABLE table方法也属于模型类的连贯操作方法之一,主要用于指定操作的数据表。 用法 一般情况下,操作模型的时候系统能够自动识别当前对应的数据表,所以,使用 table方法的情况通常是为了: 1. 切换操作的数据表; 2. 对多表进行操作; 例如: $Model->table('think_user')->where('status>1')->select(); 也可以在table方法中指定数据库,例如: $Model->table('db_name.think_user')->where('status>1')->select() ; table方法指定的数据表需要完整的表名,但可以采用下面的方式简化数据表前 ThinkPHP3.2.3完全开发手册 - 157 -© 本文档使用 看云 构建 缀的传入,例如: $Model->table('__USER__')->where('status>1')->select(); 会自动获取当前模型对应的数据表前缀来生成 think_user 数据表名称。 需要注意的是table方法不会改变数据库的连接,所以你要确保当前连接的用户 有权限操作相应的数据库和数据表。 切换数据表后,系统会自动重新获取切换 后的数据表的字段缓存信息。 如果需要对多表进行操作,可以这样使用: $Model->field('user.name,role.title') ->table('think_user user,think_role role') ->limit(10)->select(); 为了尽量避免和mysql的关键字冲突,可以建议使用数组方式定义,例如: $Model->field('user.name,role.title') ->table(array('think_user'=>'user','think_role'=>'role')) ->limit(10)->select(); 使用数组方式定义的优势是可以避免因为表名和关键字冲突而出错的情况。 一般情况下,无需调用table方法,默认会自动获取当前模型对应或者定义的 数据表。 ALIAS alias用于设置当前数据表的别名,便于使用其他的连贯操作例如join方法等。 ThinkPHP3.2.3完全开发手册 - 158 -© 本文档使用 看云 构建 示例: $Model = M('User'); $Model->alias('a')->join('__DEPT__ b ON b.user_id= a.id')->select() ; 最终生成的SQL语句类似于: SELECT * FROM think_user a INNER JOIN think_dept b ON b.user_ id= a.id DATA data方法也是模型类的连贯操作方法之一,用于设置当前要操作的数据对象的 值。 写操作 通常情况下我们都是通过create方法或者赋值的方式生成数据对象,然后写入 数据库,例如: $Model = D('User'); $Model->create(); // 这里略过具体的自动生成和验证判断 $Model->add(); 又或者直接对数据对象赋值,例如: ThinkPHP3.2.3完全开发手册 - 159 -© 本文档使用 看云 构建 $Model = M('User'); $Model->name = '流年'; $Model->email = 'thinkphp@qq.com'; $Model->add(); 那么data方法则是直接生成要操作的数据对象,例如: $Model = M('User'); $data['name'] = '流年'; $data['email'] = 'thinkphp@qq.com'; $Model->data($data)->add(); 注意:如果我们同时使用create方法和data创建数据对象的话,则最后调用 的方法有效。 data方法支持数组、对象和字符串,对象方式如下: $Model = M('User'); $obj = new \stdClass; $obj->name = '流年'; $obj->email = 'thinkphp@qq.com'; $Model->data($obj)->add(); 字符串方式用法如下: $Model = M('User'); $data = 'name=流年&email=thinkphp@qq.com'; $Model->data($data)->add(); 也可以直接在add方法中传入数据对象来新增数据,例如: ThinkPHP3.2.3完全开发手册 - 160 -© 本文档使用 看云 构建 $Model = M('User'); $data['name'] = '流年'; $data['email'] = 'thinkphp@qq.com'; $Model->add($data); 但是这种方式data参数只能使用数组。 当然data方法也可以用于更新数据,例如: $Model = M('User'); $data['id'] = 8; $data['name'] = '流年'; $data['email'] = 'thinkphp@qq.com'; $Model->data($data)->save(); 当然我们也可以直接这样用: $Model = M('User'); $data['id'] = 8; $data['name'] = '流年'; $data['email'] = 'thinkphp@qq.com'; $Model->save($data); 同样,此时data参数只能传入数组。 在调用save方法更新数据的时候 会自动判断当前的数据对象里面是否有主键值 存在,如果有的话会自动作为更新条件。也就是说,下面的用法和上面等效: $Model = M('User'); $data['name'] = '流年'; $data['email'] = 'thinkphp@qq.com'; $Model->data($data)->where('id=8')->save(); ThinkPHP3.2.3完全开发手册 - 161 -© 本文档使用 看云 构建 读操作 除了写操作外,data方法还可以用于读取当前的数据对象,例如: $User = M('User'); $map['name'] = '流年'; $User->where($map)->find(); // 读取当前数据对象 $data = $User->data(); FIELD field方法属于模型的连贯操作方法之一,主要目的是标识要返回或者操作的字 段,可以用于查询和写入操作。 用于查询 指定字段 在查询操作中field方法是使用最频繁的。 $Model->field('id,title,content')->select(); 这里使用field方法指定了查询的结果集中包含id,title,content三个字段的值。 执行的SQL相当于: SELECT id,title,content FROM table 可以给某个字段设置别名,例如: ThinkPHP3.2.3完全开发手册 - 162 -© 本文档使用 看云 构建 $Model->field('id,nickname as name')->select(); 执行的SQL语句相当于: SELECT id,nickname as name FROM table 使用SQL函数 可以在field方法中直接使用函数,例如: $Model->field('id,SUM(score)')->select(); 执行的SQL相当于: SELECT id,SUM(score) FROM table 除了select方法之外,所有的查询方法,包括find等都可以使用field方法。 使用数组参数 field方法的参数可以支持数组,例如: $Model->field(array('id','title','content'))->select(); 最终执行的SQL和前面用字符串方式是等效的。 数组方式的定义可以为某些字段定义别名,例如: $Model->field(array('id','nickname'=>'name'))->select(); 执行的SQL相当于: ThinkPHP3.2.3完全开发手册 - 163 -© 本文档使用 看云 构建 SELECT id,nickname as name FROM table 对于一些更复杂的字段要求,数组的优势则更加明显,例如: $Model->field(array('id','concat(name,'-',id)'=>'truename','LEFT(titl e,7)'=>'sub_title'))->select(); 执行的SQL相当于: SELECT id,concat(name,'-',id) as truename,LEFT(title,7) as sub_title FROM table 获取所有字段 如果有一个表有非常多的字段,需要获取所有的字段(这个也许很简单,因为不 调用field方法或者直接使用空的field方法都能做到): $Model->select(); $Model->field()->select(); $Model->field('*')->select(); 上面三个用法是等效的,都相当于执行SQL: SELECT * FROM table 但是这并不是我说的获取所有字段,我希望显式的调用所有字段(对于对性能要 求比较高的系统,这个要求并不过分,起码是一个比较好的习惯),那么OK, 仍然很简单,下面的用法可以完成预期的作用: $Model->field(true)->select(); ThinkPHP3.2.3完全开发手册 - 164 -© 本文档使用 看云 构建 field(true) 的用法会显式的获取数据表的所有字段列表,哪怕你的数据表有 100个字段。 字段排除 如果我希望获取排除数据表中的 content 字段(文本字段的值非常耗内存)之 外的所有字段值,我们就可以使用field方法的排除功能,例如下面的方式就可 以实现所说的功能: $Model->field('content',true)->select(); 则表示获取除了content之外的所有字段,要排除更多的字段也可以: $Model->field('user_id,content',true)->select(); //或者用 $Model->field(array('user_id','content'),true)->select(); 用于写入 除了查询操作之外,field方法还有一个非常重要的安全功能--字段合法性检字段合法性检 测测。field方法结合create方法使用就可以完成表单提交的字段合法性检测,如 果我们在表单提交的处理方法中使用了: $Model->field('title,email,content')->create(); 即表示表单中的合法字段只有 title , email 和 content 字段,无论用户通过什 么手段更改或者添加了浏览器的提交字段,都会直接屏蔽。因为,其他是所有字 段我们都不希望由用户提交来决定,你可以通过自动完成功能定义额外的字段写 入。 同样的,field也可以结合add和save方法,进行字段过滤,例如: ThinkPHP3.2.3完全开发手册 - 165 -© 本文档使用 看云 构建 $Model->field('title,email,content')->save($data); 如果data数据中包含有title,email,content之外的字段数据的话,也会过滤 掉。 ORDER order方法属于模型的连贯操作方法之一,用于对操作的结果排序。 用法如下: $Model->where('status=1')->order('id desc')->limit(5)->select(); 注意:连贯操作方法没有顺序,可以在select方法调用之前随便改变调用顺序。 支持对多个字段的排序,例如: $Model->where('status=1')->order('id desc,status')->limit(5)->sel ect(); 如果没有指定desc或者asc排序规则的话,默认为asc。 如果你的字段和mysql关键字有冲突,那么建议采用数组方式调用,例如: $Model->where('status=1')->order(array('order','id'=>'desc'))->lim it(5)->select(); ThinkPHP3.2.3完全开发手册 - 166 -© 本文档使用 看云 构建 LIMIT limit方法也是模型类的连贯操作方法之一,主要用于指定查询和操作的数量, 特别在分页查询的时候使用较多。ThinkPHP的limit方法可以兼容所有的数据库 驱动类的。 限制结果数量 例如获取满足要求的10个用户,如下调用即可: $User = M('User'); $User->where('status=1')->field('id,name')->limit(10)->select(); limit方法也可以用于写操作,例如更新满足要求的3条数据: $User = M('User'); $User->where('score=100')->limit(3)->save(array('level'=>'A')); 分页查询 用于文章分页查询是limit方法比较常用的场合,例如: $Article = M('Article'); $Article->limit('10,25')->select(); 表示查询文章数据,从第10行开始的25条数据(可能还取决于where条件和 order排序的影响 这个暂且不提)。 你也可以这样使用,作用是一样的: ThinkPHP3.2.3完全开发手册 - 167 -© 本文档使用 看云 构建 $Article = M('Article'); $Article->limit(10,25)->select(); 对于大数据表,尽量使用limit限制查询结果,否则会导致很大的内存开销和性 能问题。 PAGE page方法也是模型的连贯操作方法之一,是完全为分页查询而诞生的一个人性 化操作方法。 我们在前面已经了解了关于limit方法用于分页查询的情况,而page方法则是更 人性化的进行分页查询的方法,例如还是以文章列表分页为例来说,如果使用 limit方法,我们要查询第一页和第二页(假设我们每页输出10条数据)写法如 下: $Article = M('Article'); $Article->limit('0,10')->select(); // 查询第一页数据 $Article->limit('10,10')->select(); // 查询第二页数据 虽然利用扩展类库中的分页类Page可以自动计算出每个分页的limit参数,但是 如果要自己写就比较费力了,如果用page方法来写则简单多了,例如: $Article = M('Article'); $Article->page('1,10')->select(); // 查询第一页数据 $Article->page('2,10')->select(); // 查询第二页数据 显而易见的是,使用page方法你不需要计算每个分页数据的起始位置,page方 法内部会自动计算。 ThinkPHP3.2.3完全开发手册 - 168 -© 本文档使用 看云 构建 和limit方法一样,page方法也支持2个参数的写法,例如: $Article->page(1,10)->select(); // 和下面的用法等效 $Article->page('1,10')->select(); page方法还可以和limit方法配合使用,例如: $Article->limit(25)->page(3)->select(); 当page方法只有一个值传入的时候,表示第几页,而limit方法则用于设置每页 显示的数量,也就是说上面的写法等同于: $Article->page('3,25')->select(); GROUP GROUP方法也是连贯操作方法之一,通常用于结合合计函数,根据一个或多个 列对结果集进行分组 。 group方法只有一个参数,并且只能使用字符串。 例如,我们都查询结果按照用户id进行分组统计: $this->field('username,max(score)')->group('user_id')->select(); 生成的SQL语句是: ThinkPHP3.2.3完全开发手册 - 169 -© 本文档使用 看云 构建 SELECT username,max(score) FROM think_score GROUP BY user_i d 也支持对多个字段进行分组,例如: $this->field('username,max(score)')->group('user_id,test_time')->s elect(); 生成的SQL语句是: SELECT username,max(score) FROM think_score GROUP BY user_i d,test_time HAVING HAVING方法也是连贯操作之一,用于配合group方法完成从分组的结果中筛 选(通常是聚合条件)数据。 having方法只有一个参数,并且只能使用字符串,例如: $this->field('username,max(score)')->group('user_id')->having('cou nt(test_time)>3')->select(); 生成的SQL语句是: SELECT username,max(score) FROM think_score GROUP BY user_i d HAVING count(test_time)>3 ThinkPHP3.2.3完全开发手册 - 170 -© 本文档使用 看云 构建 JOIN JOIN方法也是连贯操作方法之一,用于根据两个或多个表中的列之间的关系, 从这些表中查询数据。 join通常有下面几种类型,不同类型的join操作会影响返回的数据结果。 INNER JOININNER JOIN : 如果表中有至少一个匹配,则返回行,等同于 JOIN LEFT JOINLEFT JOIN : 即使右表中没有匹配,也从左表返回所有的行 RIGHT JOINRIGHT JOIN : 即使左表中没有匹配,也从右表返回所有的行 FULL JOINFULL JOIN : 只要其中一个表中存在匹配,就返回行 join方法可以支持以上四种类型,例如: $Model = M('Artist'); $Model ->join('think_work ON think_artist.id = think_work.artist_id') ->join('think_card ON think_artist.card_id = think_card.id') ->select(); join方法支持多次调用,但指定的数据表必须是全称,但我们可以这样来定义: $Model ->join('__WORK__ ON __ARTIST__.id = __WORK__.artist_id') ->join('__CARD__ ON __ARTIST__.card_id = __CARD__.id') ->select(); __WORK__ 和 __CARD__ 在最终解析的时候会转换为 think_work 和 think_card 。 默认采用INNER JOIN 方式,如果需要用其他的JOIN方式,可以改成 ThinkPHP3.2.3完全开发手册 - 171 -© 本文档使用 看云 构建 $Model->join('RIGHT JOIN __WORK__ ON __ARTIST__.id = __WOR K__.artist_id')->select(); 或者使用: $Model->join('__WORK__ ON __ARTIST__.id = __WORK__.artist_id',' RIGHT')->select(); join方法的第二个参数支持的类型包括:INNER LEFT RIGHT FULL。 如果join方法的参数用数组的话,只能使用一次join方法,并且不能和字符串方 式混合使用。 例如: join(array(' __WORK__ ON __ARTIST__.id = __WORK__.artist_id','__CA RD__ ON __ARTIST__.card_id = __CARD__.id')) 使用数组方式的情况下,第二个参数无效。因此必须在字符串中显式定义join类 型,例如: join(array(' LEFT JOIN __WORK__ ON __ARTIST__.id = __WORK__.art ist_id','RIGHT JOIN __CARD__ ON __ARTIST__.card_id = __CARD__.id' )) UNION UNION操作用于合并两个或多个 SELECT 语句的结果集。 使用示例: ThinkPHP3.2.3完全开发手册 - 172 -© 本文档使用 看云 构建 $Model->field('name') ->table('think_user_0') ->union('SELECT name FROM think_user_1') ->union('SELECT name FROM think_user_2') ->select(); 数组用法: $Model->field('name') ->table('think_user_0') ->union(array('field'=>'name','table'=>'think_user_1')) ->union(array('field'=>'name','table'=>'think_user_2')) ->select(); 或者 $Model->field('name') ->table('think_user_0') ->union(array('SELECT name FROM think_user_1','SELECT nam e FROM think_user_2')) ->select(); 支持UNION ALL 操作,例如: $Model->field('name') ->table('think_user_0') ->union('SELECT name FROM think_user_1',true) ->union('SELECT name FROM think_user_2',true) ->select(); 或者 ThinkPHP3.2.3完全开发手册 - 173 -© 本文档使用 看云 构建 $Model->field('name') ->table('think_user_0') ->union(array('SELECT name FROM think_user_1','SELECT nam e FROM think_user_2'),true) ->select(); 每个union方法相当于一个独立的SELECT语句。 注意:UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有 相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。 DISTINCT DISTINCT 方法用于返回唯一不同的值 。 例如: $Model->distinct(true)->field('name')->select(); 生成的SQL语句是: SELECT DISTINCT name FROM think_user distinct方法的参数是一个布尔值。 LOCK Lock方法是用于数据库的锁机制,如果在查询或者执行操作的时候使用: lock(true); ThinkPHP3.2.3完全开发手册 - 174 -© 本文档使用 看云 构建 就会自动在生成的SQL语句最后加上 FOR UPDATE 或者 FOR UPDATE NOWAIT (Oracle数据库)。 CACHE cache方法用于查询缓存操作,也是连贯操作方法之一。 cachecache 可以用于 select 、 find 和 getField 方法,以及其衍生方法,使用 cache方法后,在缓存有效期之内不会再次进行数据库查询操作,而是直接获取 缓存中的数据,关于数据缓存的类型和设置可以参考缓存部分。 下面举例说明,例如,我们对find方法使用cache方法如下: $Model = M('User'); $Model->where('id=5')->cache(true)->find(); 第一次查询结果会被缓存,第二次查询相同的数据的时候就会直接返回缓存中的 内容,而不需要再次进行数据库查询操作。 默认情况下, 缓存有效期和缓存类型是 由DAT A_CACHE_T IM EDAT A_CACHE_T IM E 和DAT A_CACHE_T YPEDAT A_CACHE_T YPE 配置参数决定的,但 cache方法可以单独指定,例如: $Model = M('User'); $Model->cache(true,60,'xcache')->find(); 表示对查询结果使用xcache缓存,缓存有效期60秒。 cache方法可以指定缓存标识: ThinkPHP3.2.3完全开发手册 - 175 -© 本文档使用 看云 构建 $Model = M('User'); $Model->cache('key',60)->find(); 指定查询缓存的标识可以使得查询缓存更有效率。 这样,在外部就可以通过S方法直接获取查询缓存的数据,例如: $Model = M('User'); $result = $Model->cache('key',60)->find(); $data = S('key'); COMMENT COMMENT方法 用于在生成的SQL语句中添加注释内容,例如: $this->comment('查询考试前十名分数') ->field('username,score') ->limit(10) ->order('score desc') ->select(); 最终生成的SQL语句是: SELECT username,score FROM think_score ORDER BY score desc LIMIT 10 /* 查询考试前十名分数 */ RELATION ThinkPHP3.2.3完全开发手册 - 176 -© 本文档使用 看云 构建 USING fetchSql fetchSql用于直接返回SQL而不是执行查询,适用于任何的CURD操作方法。 例如: $result = M('User')->fetchSql(true)->find(1); 输出result结果为: SELECT * FROM think_user where id = 1 TOKEN token方法可用于临时关闭令牌验证,例如: $model->token(false)->create(); 即可在提交表单的时候临时关闭令牌验证(即使开启了TOKEN_ON参数)。 STRICT strict为3.2.3新增连贯操作,用于设置数据写入和查询是否严格检查是否存在字 段。默认情况下不合法数据字段自动删除,如果设置了严格检查则会抛出异常。 ThinkPHP3.2.3完全开发手册 - 177 -© 本文档使用 看云 构建 例如: $model->strict(true)->add($data); INDEX index方法用于数据集的强制索引操作,例如: $Model->index('user')->select(); 对查询强制使用user索引,user必须是数据表实际创建的索引名称。 命名范围 在应用开发过程中,使用最多的操作还是数据查询操作,凭借ThinkPHP的连贯 操作的特性,可以使得查询操作变得更优雅和清晰,命名范围功能则是给模型操 作定义了一系列的封装,让你更方便的操作数据。 命名范围功能的优势在于可以一次定义多次调用,并且在项目中也能起到分工配 合的规范,避免开发人员在写CURD操作的时候出现问题,项目经理只需要合理 的规划命名范围即可。 定义属性 要使用命名范围功能,主要涉及到模型类的 _scope 属性定义和 scope 连贯操 作方法的使用。 我们首先定义_scope属性: ThinkPHP3.2.3完全开发手册 - 178 -© 本文档使用 看云 构建 namespace Home\Model; use Think\Model; class NewsModel extends Model { protected $_scope = array( // 命名范围normal 'normal'=>array( 'where'=>array('status'=>1), ), // 命名范围latest 'latest'=>array( 'order'=>'create_time DESC', 'limit'=>10, ), ); } _scope 属性是一个数组,每个数组项表示定义一个命名范围,命名范围的定义 格式为: '命名范围标识'=>array( '属性1'=>'值1', '属性2'=>'值2', ... ) 命名范围标识:可以是任意的字符串,用于标识当前定义的命名范围名称。 命名范围支持的属性包括: 属性属性 描述描述 where 查询条件 field 查询字段 ThinkPHP3.2.3完全开发手册 - 179 -© 本文档使用 看云 构建 order 结果排序 table 查询表名 limit 结果限制 page 结果分页 having having查询 group group查询 lock 查询锁定 distinct 唯一查询 cache 查询缓存 属性属性 描述描述 每个命名范围的定义可以包括这些属性中一个或者多个。 方法调用 属性定义完成后,接下来就是使用 scope 方法进行命名范围的调用了,每调用 一个命名范围,就相当于执行了命名范围中定义的相关操作选项对应的连贯操作 方法。 调用某个命名范围 最简单的调用方式就直接调用某个命名范围,例如: $Model = D('News'); // 这里必须使用D方法 因为命名范围在模型里面定 义 $Model->scope('normal')->select(); $Model->scope('latest')->select(); 生成的SQL语句分别是: ThinkPHP3.2.3完全开发手册 - 180 -© 本文档使用 看云 构建 SELECT * FROM think_news WHERE status=1 SELECT * FROM think_news ORDER BY create_time DESC LIMIT 1 0 调用多个命名范围 也可以支持同时调用多个命名范围定义,例如: $Model->scope('normal')->scope('latest')->select(); 或者简化为: $Model->scope('normal,latest')->select(); 生成的SQL都是: SELECT * FROM think_news WHERE status=1 ORDER BY create_ti me DESC LIMIT 10 如果两个命名范围的定义存在冲突,则后面调用的命名范围定义会覆盖前面的相 同属性的定义。 如果调用的命名范围标识不存在,则会忽略该命名范围,例如: $Model->scope('normal,new')->select(); 上面的命名范围中new是不存在的,因此只有normal命名范围生效,生成的 SQL语句是: SELECT * FROM think_news WHERE status=1 ThinkPHP3.2.3完全开发手册 - 181 -© 本文档使用 看云 构建 默认命名范围 系统支持默认命名范围功能,如果你定义了一个default命名范围,例如: protected $_scope = array( // 默认的命名范围 'default'=>array( 'where'=>array('status'=>1), 'limit'=>10, ), ); 那么调用default命名范围可以直接使用: $Model->scope()->select(); 而无需再传入命名范围标识名 $Model->scope('default')->select(); 虽然这两种方式是等效的。 命名范围调整 如果你需要在normal命名范围的基础上增加额外的调整,可以使用: $Model->scope('normal',array('limit'=>5))->select(); 生成的SQL语句是: SELECT * FROM think_news WHERE status=1 LIMIT 5 ThinkPHP3.2.3完全开发手册 - 182 -© 本文档使用 看云 构建 当然,也可以在两个命名范围的基础上进行调整,例如: $Model->scope('normal,latest',array('limit'=>5))->select(); 生成的SQL是: SELECT * FROM think_news WHERE status=1 ORDER BY create_ti me DESC LIMIT 5 自定义命名范围 又或者,干脆不用任何现有的命名范围,我直接传入一个命名范围: $Model->scope(array('field'=>'id,title','limit'=>5,'where'=>'status= 1','order'=>'create_time DESC'))->select(); 这样,生成的SQL变成: SELECT id,title FROM think_news WHERE status=1 ORDER BY cre ate_time DESC LIMIT 5 与连贯操作混合使用 命名范围一样可以和之前的连贯操作混合使用,例如定义了命名范围_scope属 性: ThinkPHP3.2.3完全开发手册 - 183 -© 本文档使用 看云 构建 protected $_scope = array( 'normal'=>array( 'where'=>array('status'=>1), 'field'=>'id,title', 'limit'=>10, ), ); 然后在使用的时候,可以这样调用: $Model->scope('normal')->limit(8)->order('id desc')->select(); 这样,生成的SQL变成: SELECT id,title FROM think_news WHERE status=1 ORDER BY id d esc LIMIT 8 如果定义的命名范围和连贯操作的属性有冲突,则后面调用的会覆盖前面的。 如果是这样调用: $Model->limit(8)->scope('normal')->order('id desc')->select(); 生成的SQL则是: SELECT id,title FROM think_news WHERE status=1 ORDER BY id d esc LIMIT 10 动态调用 除了采用scope方法调用命名范围外,我们还支持直接调用命名范围名称的方式 来动态调用,例如: ThinkPHP3.2.3完全开发手册 - 184 -© 本文档使用 看云 构建 $Model->scope('normal',array('limit'=>5))->select(); 查询操作也可以采用: $Model->normal(array('limit'=>5))->select(); 的方式调用。 normal(array('limit'=>5)) 表示调用normal命名范围,并且传 入额外的 array('limit'=>5) 参数。 由于采用的是 __call 魔术方法机制,因此这样调用的前提是你定义的命名范围名 称没有和现有操作方法冲突。 CURD操作 ThinkPHP提供了灵活和方便的数据操作方法,对数据库操作的四个基本操作 (CURD):创建、更新、读取和删除的实现是最基本的,也是必须掌握的,在 这基础之上才能熟悉更多实用的数据操作方法。 CURD操作通常是可以和连贯操作配合完成的。 数据创建 在进行数据操作之前,我们往往需要手动创建需要的数据,例如对于提交的表单 数据: ThinkPHP3.2.3完全开发手册 - 185 -© 本文档使用 看云 构建 // 获取表单的POST数据 $data['name'] = $_POST['name']; $data['email'] = $_POST['email']; // 更多的表单数据值获取 //…… 创建数据对象 ThinkPHP可以帮助你快速地创建数据对象,最典型的应用就是自动根据表单数 据创建数据对象,这个优势在一个数据表的字段非常之多的情况下尤其明显。 很简单的例子: // 实例化User模型 $User = M('User'); // 根据表单提交的POST数据创建数据对象 $User->create(); Create方法支持从其它方式创建数据对象,例如,从其它的数据对象,或者数 组等 $data['name'] = 'ThinkPHP'; $data['email'] = 'ThinkPHP@gmail.com'; $User->create($data); 甚至还可以支持从对象创建新的数据对象 ThinkPHP3.2.3完全开发手册 - 186 -© 本文档使用 看云 构建 // 从User数据对象创建新的Member数据对象 $User = stdClass(); $User->name = 'ThinkPHP'; $User->email = 'ThinkPHP@gmail.com'; $Member = M("Member"); $Member->create($User); 创建完成的数据可以直接读取和修改,例如: $data['name'] = 'ThinkPHP'; $data['email'] = 'ThinkPHP@gmail.com'; $User->create($data); // 创建完成数据对象后可以直接读取数据 echo $User->name; echo $User->email; // 也可以直接修改创建完成的数据 $User->name = 'onethink'; // 修改name字段数据 $User->status = 1; // 增加新的字段数据 数据操作状态 create方法的第二个参数可以指定创建数据的操作状态,默认情况下是自动判 断是写入还是更新操作。 也可以显式指定操作状态,例如: $Member = M("User"); // 指定更新数据操作状态 $Member->create($_POST,Model::MODEL_UPDATE); 系统内置的数据操作包括 Model::MODEL_INSERT (或者1)和 Model::MODEL_UPDATE (或者2),当没有指定的时候,系统根据数据源 是否包含主键数据来自动判断,如果存在主键数据,就当成 ThinkPHP3.2.3完全开发手册 - 187 -© 本文档使用 看云 构建 Model::MODEL_UPDATE 操作。 不同的数据操作状态可以定义不同的数据验证和自动完成机制,所以,你可以自 定义自己需要的数据操作状态,例如,可以设置登录操作的数据状态(假设为 3): $Member = M("User"); // 指定更新数据操作状态 $Member->create($_POST,3); 事实上,create方法所做的工作远非这么简单,在创建数据对象的同时,完成 了一系列的工作,我们来看下create方法的工作流程就能明白: 步步 骤骤 说明说明 返回返回 1 获取数据源(默认是POST数组) 2 验证数据源合法性(非数组或者对象会过滤) 失败则返回 false 3 检查字段映射 4 判断数据状态(新增或者编辑,指定或者自动判 断) 5 数据自动验证 失败则返回 false 6 表单令牌验证 失败则返回 false 7 表单数据赋值(过滤非法字段和字符串处理) 8 数据自动完成 9 生成数据对象(保存在内存) ThinkPHP3.2.3完全开发手册 - 188 -© 本文档使用 看云 构建 因此,我们熟悉的令牌验证、自动验证和自动完成功能,其实都必须通过 create方法才能生效。 如果没有定义自动验证的话,create方法的返回值是创建完成的数据对象数 组,例如: $data['name'] = 'thinkphp'; $data['email'] = 'thinkphp@gmail.com'; $data['status'] = 1; $User = M('User'); $data = $User->create($data); dump($data); 输出结果为: array (size=3) 'name' => string 'thinkphp' (length=8) 'email' => string 'thinkphp@gmail.com' (length=18) 'status'=> int 1 Create方法创建的数据对象是保存在内存中,并没有实际写入到数据库中,直 到使用 add 或者 save 方法才会真正写入数据库。 因此在没有调用add或者save方法之前,我们都可以改变create方法创建的数 据对象,例如: $User = M('User'); $User->create(); //创建User数据对象 $User->status = 1; // 设置默认的用户状态 $User->create_time = time(); // 设置用户的创建时间 $User->add(); // 把用户对象写入数据库 如果只是想简单创建一个数据对象,并不需要完成一些额外的功能的话,可以使 ThinkPHP3.2.3完全开发手册 - 189 -© 本文档使用 看云 构建 用data方法简单的创建数据对象。 使用如下: // 实例化User模型 $User = M('User'); // 创建数据后写入到数据库 $data['name'] = 'ThinkPHP'; $data['email'] = 'ThinkPHP@gmail.com'; $User->data($data)->add(); Data方法也支持传入数组和对象,使用data方法创建的数据对象不会进行自动 验证和过滤操作,请自行处理。但在进行add或者save操作的时候,数据表中不 存在的字段以及非法的数据类型(例如对象、数组等非标量数据)是会自动过滤 的,不用担心非数据表字段的写入导致SQL错误的问题。 支持的连贯操作 在执行create方法之前,我们可以调用相关的连贯操作方法,配合完成数据创 建操作。 create方法支持的连贯操作方法包括: 连贯操作连贯操作 作用作用 支持的参数类型支持的参数类型 field 用于定义合法的字段 字符串和数组 validate 用于数据自动验证 数组 auto 用于数据自动完成 数组 token 用于令牌验证 布尔值 更多的用法参考后续的内容。 字段合法性过滤 如果在create方法之前调用field方法,则表示只允许创建指定的字段数据,其 他非法字段将会被过滤,例如: ThinkPHP3.2.3完全开发手册 - 190 -© 本文档使用 看云 构建 $data['name'] = 'thinkphp'; $data['email'] = 'thinkphp@gmail.com'; $data['status'] = 1; $data['test'] = 'test'; $User = M('User'); $data = $User->field('name,email')->create($data); dump($data); 输出结果为: array (size=2) 'name' => string 'thinkphp' (length=8) 'email' => string 'thinkphp@gmail.com' (length=18) 最终只有 name 和 email 字段的数据被允许写入, status 和 test 字段直接被 过滤了,哪怕status也是数据表中的合法字段。 如果我们有自定义模型类,对于数据新增和编辑操作的话,我们还可以直接在模 型类里面通过设置 insertFields 和 updateFields 属性来定义允许的字段,例 如: namespace Home\Model; use Think\Model; class UserModel extends Model{ protected $insertFields = 'name,email'; // 新增数据的时候允许写入 name和email字段 protected $updateFields = 'email'; // 编辑数据的时候只允许写入em ail字段 } ThinkPHP3.2.3完全开发手册 - 191 -© 本文档使用 看云 构建 数据写入 ThinkPHP的数据写入操作使用add方法add方法,使用示例如下: $User = M("User"); // 实例化User对象 $data['name'] = 'ThinkPHP'; $data['email'] = 'ThinkPHP@gmail.com'; $User->add($data); 如果是Mysql数据库的话,还可以支持在数据插入时允许更新操作: add($data='',$options=array(),$replace=false) 其中add方法增加$replace参数(是否添加数据时允许覆盖),true表示覆盖,默 认为false 或者使用data方法连贯操作 $User = M("User"); // 实例化User对象 $User->data($data)->add(); 如果在add之前已经创建数据对象的话(例如使用了create或者data方 法),add方法就不需要再传入数据了。 使用create方法的例子: ThinkPHP3.2.3完全开发手册 - 192 -© 本文档使用 看云 构建 $User = M("User"); // 实例化User对象 // 根据表单提交的POST数据创建数据对象 if($User->create()){ $result = $User->add(); // 写入数据到数据库 if($result){ // 如果主键是自动增长型 成功后返回值就是最新插入的值 $insertId = $result; } } create方法并不算是连贯操作,因为其返回值可能是布尔值,所以必须要进 行严格判断。 支持的连贯操作 在执行add方法之前,我们可以调用相关的连贯操作方法,配合完成数据写入操 作。 写入操作支持的连贯操作方法包括: 连贯操作连贯操作 作用作用 支持的参数类型支持的参数类型 table 用于定义要操作的数据表名称 字符串和数组 data 用于指定要写入的数据对象 数组和对象 field 用于定义要写入的字段 字符串和数组 relation 用于关联查询(需要关联模型支持) 字符串 validate 用于数据自动验证 数组 auto 用于数据自动完成 数组 filter 用于数据过滤 字符串 scope 用于命名范围 字符串、数组 bind 用于数据绑定操作 数组 ThinkPHP3.2.3完全开发手册 - 193 -© 本文档使用 看云 构建 token 用于令牌验证 布尔值 comment 用于SQL注释 字符串 fetchSql 不执行SQL而只是返回SQL 布尔值 连贯操作连贯操作 作用作用 支持的参数类型支持的参数类型 可以支持不执行SQL而只是返回SQL语句,例如: $User = M("User"); // 实例化User对象 $data['name'] = 'ThinkPHP'; $data['email'] = 'ThinkPHP@gmail.com'; $sql = $User->fetchSql(true)->add($data); echo $sql; // 输出结果类似于 // INSERT INTO think_user (name,email) VALUES ('ThinkPHP','Thin kPHP@gmail.com') 字段过滤 如果写入了数据表中不存在的字段数据,则会被直接过滤,例如: $data['name'] = 'thinkphp'; $data['email'] = 'thinkphp@gmail.com'; $data['test'] = 'test'; $User = M('User'); $User->data($data)->add(); 其中test字段是不存在的,所以写入数据的时候会自动过滤掉。 在3.2.2版本以上,如果开启调试模式的话,则会抛出异常,提示: 非法数据对象:[test=>test] 如果在add方法之前调用field方法,则表示只允许写入指定的字段数据,其他非 法字段将会被过滤,例如: ThinkPHP3.2.3完全开发手册 - 194 -© 本文档使用 看云 构建 $data['name'] = 'thinkphp'; $data['email'] = 'thinkphp@gmail.com'; $data['test'] = 'test'; $User = M('User'); $User->field('name')->data($data)->add(); 最终只有name字段的数据被允许写入,email和test字段直接被过滤了,哪怕 email也是数据表中的合法字段。 字段内容过滤 通过filter方法可以对数据的值进行过滤处理,例如: $data['name'] = 'thinkphp'; $data['email'] = 'thinkphp@gmail.com'; $User = M('User'); $User->data($data)->filter('strip_tags')->add(); 写入数据库的时候会把name字段的值转化为 thinkphp 。 filter方法的参数是一个回调类型,支持函数或者闭包定义。 批量写入 在某些情况下可以支持数据的批量写入,例如: // 批量添加数据 $dataList[] = array('name'=>'thinkphp','email'=>'thinkphp@gamil.c om'); $dataList[] = array('name'=>'onethink','email'=>'onethink@gamil.c om'); $User->addAll($dataList); ThinkPHP3.2.3完全开发手册 - 195 -© 本文档使用 看云 构建 该功能需要3.2.3以上版本,3.2.3以下版本仅对mysql数据库支持该功能需要3.2.3以上版本,3.2.3以下版本仅对mysql数据库支持 数据读取 在ThinkPHP中读取数据的方式很多,通常分为读取数据、读取数据集和读取字 段值。 数据查询方法支持的连贯操作方法有: 连贯操作连贯操作 作用作用 支持的参数类型支持的参数类型 where 用于查询或者更新条件的定义 字符串、数组和对 象 table 用于定义要操作的数据表名称 字符串和数组 alias 用于给当前数据表定义别名 字符串 field 用于定义要查询的字段(支持字段排 除) 字符串和数组 order 用于对结果排序 字符串和数组 group 用于对查询的group支持 字符串 having 用于对查询的having支持 字符串 join 用于对查询的join支持 字符串和数组 union 用于对查询的union支持 字符串、数组和对 象 distinct 用于查询的distinct支持 布尔值 lock 用于数据库的锁机制 布尔值 cache 用于查询缓存 支持多个参数 ThinkPHP3.2.3完全开发手册 - 196 -© 本文档使用 看云 构建 relation 用于关联查询(需要关联模型支持) 字符串 result 用于返回数据转换 字符串 scope 用于命名范围 字符串、数组 bind 用于数据绑定操作 数组 comment 用于SQL注释 字符串 fetchSql 不执行SQL而只是返回SQL 布尔值 连贯操作连贯操作 作用作用 支持的参数类型支持的参数类型 注意:某些情况下有些连贯操作是无效的,例如limit方法对find方法是无效 的。 读取数据 读取数据是指读取数据表中的一行数据(或者关联数据),主要通过 find 方法 完成,例如: $User = M("User"); // 实例化User对象 // 查找status值为1name值为think的用户数据 $data = $User->where('status=1 AND name="thinkphp"')->find(); dump($data); find方法查询数据的时候可以配合相关的连贯操作方法,其中最关键的则是 where方法,如何使用where方法我们会在查询语言章节中详细描述。 如果查询出错,find方法返回false,如果查询结果为空返回NULL,查询成功则 返回一个关联数组(键值是字段名或者别名)。 如果上面的查询成功的话,会 输出: ThinkPHP3.2.3完全开发手册 - 197 -© 本文档使用 看云 构建 array (size=3) 'name' => string 'thinkphp' (length=8) 'email' => string 'thinkphp@gmail.com' (length=18) 'status'=> int 1 即使满足条件的数据不止一个,find方法也只会返回第一条记录(可以通过 order方法排序后查询)。 还可以用data方法获取查询后的数据对象(查询成功后) $User = M("User"); // 实例化User对象 // 查找status值为1name值为think的用户数据 $User->where('status=1 AND name="thinkphp"')->find(); dump($User->data()); 读取数据集 读取数据集其实就是获取数据表中的多行记录(以及关联数据),使用 select 方法,使用示例: $User = M("User"); // 实例化User对象 // 查找status值为1的用户数据 以创建时间排序 返回10条数据 $list = $User->where('status=1')->order('create_time')->limit(10)-> select(); 如果查询出错,select的返回值是false,如果查询结果为空,则返回NULL,否 则返回二维数组。 读取字段值 读取字段值其实就是获取数据表中的某个列的多个或者单个数据,最常用的方法 是 getField 方法。 ThinkPHP3.2.3完全开发手册 - 198 -© 本文档使用 看云 构建 示例如下: $User = M("User"); // 实例化User对象 // 获取ID为3的用户的昵称 $nickname = $User->where('id=3')->getField('nickname'); 默认情况下,当只有一个字段的时候,返回满足条件的数据表中的该字段的第一 行的值。 如果需要返回整个列的数据,可以用: $User->getField('id',true); // 获取id数组 //返回数据格式如array(1,2,3,4,5)一维数组,其中value就是id列的每行的 值 如果传入多个字段的话,默认返回一个关联数组: $User = M("User"); // 实例化User对象 // 获取所有用户的ID和昵称列表 $list = $User->getField('id,nickname'); //两个字段的情况下返回的是array(`id`=>`nickname`)的关联数组,以id的 值为key,nickname字段值为value 这样返回的list是一个数组,键名是用户的id字段的值,键值是用户的昵称 nickname。 如果传入多个字段的名称,例如: $list = $User->getField('id,nickname,email'); //返回的数组格式是array(`id`=>array(`id`=>value,`nickname`=>value,` email`=>value))是一个二维数组,key还是id字段的值,但value是整行的 array数组,类似于select()方法的结果遍历将id的值设为数组key ThinkPHP3.2.3完全开发手册 - 199 -© 本文档使用 看云 构建 返回的是一个二维数组,类似select方法的返回结果,区别的是这个二维数组的 键名是用户的id(准确的说是getField方法的第一个字段名)。 如果我们传入一个字符串分隔符: $list = $User->getField('id,nickname,email',':'); 那么返回的结果就是一个数组,键名是用户id,键值是 nickname:email 的输 出字符串。 getField方法还可以支持限制数量,例如: $this->getField('id,name',5); // 限制返回5条记录 $this->getField('id',3); // 获取id数组 限制3条记录 可以配合使用order方法使用。更多的查询方法可以参考查询语言章节。 数据更新 ThinkPHP的数据更新操作包括更新数据和更新字段方法。 更新数据 更新数据使用 save 方法,例如: $User = M("User"); // 实例化User对象 // 要修改的数据对象属性赋值 $data['name'] = 'ThinkPHP'; $data['email'] = 'ThinkPHP@gmail.com'; $User->where('id=5')->save($data); // 根据条件更新记录 ThinkPHP3.2.3完全开发手册 - 200 -© 本文档使用 看云 构建 也可以改成对象方式来操作: $User = M("User"); // 实例化User对象 // 要修改的数据对象属性赋值 $User->name = 'ThinkPHP'; $User->email = 'ThinkPHP@gmail.com'; $User->where('id=5')->save(); // 根据条件更新记录 数据对象赋值的方式,save方法无需传入数据,会自动识别。 注意:save方法的返回值是影响的记录数影响的记录数,如果返回false则表示更新出 错,因此一定要用恒等来判断是否更新失败。 为了保证数据库的安全,避免出错更新整个数据表,如果没有任何更新条件,数 据对象本身也不包含主键字段的话,save方法不会更新任何数据库的记录。 因此下面的代码不会更改数据库的任何记录 $User->save($data); 除非使用下面的方式: $User = M("User"); // 实例化User对象 // 要修改的数据对象属性赋值 $data['id'] = 5; $data['name'] = 'ThinkPHP'; $data['email'] = 'ThinkPHP@gmail.com'; $User->save($data); // 根据条件保存修改的数据 如果id是数据表的主键的话,系统自动会把主键的值作为更新条件来更新其他字 段的值。 数据更新方法支持的连贯操作方法有: ThinkPHP3.2.3完全开发手册 - 201 -© 本文档使用 看云 构建 连贯操作连贯操作 作用作用 支持的参数类型支持的参数类型 where 用于查询或者更新条件的定义 字符串、数组和对象 table 用于定义要操作的数据表名称 字符串和数组 alias 用于给当前数据表定义别名 字符串 field 用于定义允许更新的字段 字符串和数组 order 用于对数据排序 字符串和数组 lock 用于数据库的锁机制 布尔值 relation 用于关联更新(需要关联模型支持) 字符串 scope 用于命名范围 字符串、数组 bind 用于数据绑定操作 数组 comment 用于SQL注释 字符串 fetchSql 不执行SQL而只是返回SQL 布尔值 字段和数据过滤 和add方法一样,save方法支持使用 field 方法过滤字段和 filter 方法过滤数 据,例如: $User = M("User"); // 实例化User对象 // 要修改的数据对象属性赋值 $data['name'] = 'test'; $data['email'] = 'test@gmail.com'; $User->where('id=5')->field('email')->filter('strip_tags')->save($dat a); // 根据条件保存修改的数据 当使用field('email')的时候,只允许更新email字段的值(采用strip_tags方法 过滤),name字段的值将不会被修改。 ThinkPHP3.2.3完全开发手册 - 202 -© 本文档使用 看云 构建 还有一种方法是通过create或者data方法创建要更新的数据对象,然后进行保 存操作,这样save方法的参数可以不需要传入。 $User = M("User"); // 实例化User对象 // 要修改的数据对象属性赋值 $data['name'] = 'ThinkPHP'; $data['email'] = 'ThinkPHP@gmail.com'; $User->where('id=5')->data($data)->save(); // 根据条件保存修改的数 据 使用create方法的例子: $User = M("User"); // 实例化User对象 // 根据表单提交的POST数据创建数据对象 $User->create(); $User->save(); // 根据条件保存修改的数据 更新字段 如果只是更新个别字段的值,可以使用 setField 方法。 使用示例: $User = M("User"); // 实例化User对象 // 更改用户的name值 $User-> where('id=5')->setField('name','ThinkPHP'); setField方法支持同时更新多个字段,只需要传入数组即可,例如: ThinkPHP3.2.3完全开发手册 - 203 -© 本文档使用 看云 构建 $User = M("User"); // 实例化User对象 // 更改用户的name和email的值 $data = array('name'=>'ThinkPHP','email'=>'ThinkPHP@gmail.com' ); $User-> where('id=5')->setField($data); 而对于统计字段(通常指的是数字类型)的更新,系统还提供了 setInc 和 setDec 方法。 $User = M("User"); // 实例化User对象 $User->where('id=5')->setInc('score',3); // 用户的积分加3 $User->where('id=5')->setInc('score'); // 用户的积分加1 $User->where('id=5')->setDec('score',5); // 用户的积分减5 $User->where('id=5')->setDec('score'); // 用户的积分减1 3.2.3版本开始,setInc和setDec方法支持延迟更新,用法如下: $Article = M("Article"); // 实例化Article对象 $Article->where('id=5')->setInc('view',1); // 文章阅读数加1 $Article->where('id=5')->setInc('view',1,60); // 文章阅读数加1,并且 延迟60秒更新(写入) 数据删除 ThinkPHP删除数据使用delete方法,例如: $Form = M('Form'); $Form->delete(5); ThinkPHP3.2.3完全开发手册 - 204 -© 本文档使用 看云 构建 表示删除主键为5的数据,delete方法可以删除单个数据,也可以删除多个数 据,这取决于删除条件,例如: $User = M("User"); // 实例化User对象 $User->where('id=5')->delete(); // 删除id为5的用户数据 $User->delete('1,2,5'); // 删除主键为1,2和5的用户数据 $User->where('status=0')->delete(); // 删除所有状态为0的用户数据 delete方法的返回值是删除的记录数,如果返回值是false则表示SQL出错,返 回值如果为0表示没有删除任何数据。 也可以用order和limit方法来限制要删除的个数,例如: // 删除所有状态为0的5 个用户数据 按照创建时间排序 $User->where('status=0')->order('create_time')->limit('5')->delete (); 为了避免错删数据,如果没有传入任何条件进行删除操作的话,不会执行删除操 作,例如: $User = M("User"); // 实例化User对象 $User->delete(); 不会删除任何数据,如果你确实要删除所有的记录,除非使用下面的方式: $User = M("User"); // 实例化User对象 $User->where('1')->delete(); 数据删除方法支持的连贯操作方法有: 连贯操作连贯操作 作用作用 支持的参数类型支持的参数类型 ThinkPHP3.2.3完全开发手册 - 205 -© 本文档使用 看云 构建 where 用于查询或者更新条件的定义 字符串、数组和对象 table 用于定义要操作的数据表名称 字符串和数组 alias 用于给当前数据表定义别名 字符串 order 用于对数据排序 字符串和数组 lock 用于数据库的锁机制 布尔值 relation 用于关联删除(需要关联模型支持) 字符串 scope 用于命名范围 字符串、数组 bind 用于数据绑定操作 数组 comment 用于SQL注释 字符串 fetchSql 不执行SQL而只是返回SQL 布尔值 连贯操作连贯操作 作用作用 支持的参数类型支持的参数类型 ActiveRecord ThinkPHP实现了ActiveRecords模式的ORM模型,采用了非标准的ORM模 型:表映射到类,记录映射到对象。最大的特点就是使用方便和便于理解(因为 采用了对象化),提供了开发的最佳体验,从而达到敏捷开发的目的。 下面我们用AR模式来换一种方式重新完成CURD操作。 创建数据 $User = M("User"); // 实例化User对象 // 然后直接给数据对象赋值 $User->name = 'ThinkPHP'; $User->email = 'ThinkPHP@gmail.com'; // 把数据对象添加到数据库 $User->add(); ThinkPHP3.2.3完全开发手册 - 206 -© 本文档使用 看云 构建 如果使用了create方法创建数据对象的话,仍然可以在创建完成后进行赋值 $User = D("User"); $User->create(); // 创建User数据对象,默认通过表单提交的数据进行创 建 // 增加或者更改其中的属性 $User->status = 1; $User->create_time = time(); // 把数据对象添加到数据库 $User->add(); 查询记录 AR模式的数据查询比较简单,因为更多情况下面查询条件都是以主键或者某个 关键的字段。这种类型的查询,ThinkPHP有着很好的支持。 先举个最简单的 例子,假如我们要查询主键为8的某个用户记录,如果按照之前的方式,我们可 能会使用下面的方法: $User = M("User"); // 实例化User对象 // 查找id为8的用户数据 $User->where('id=8')->find(); 用AR模式的话可以直接写成: $User->find(8); 如果要根据某个字段查询,例如查询姓名为ThinkPHP的可以用: $User = M("User"); // 实例化User对象 $User->getByName("ThinkPHP"); 这个作为查询语言来说是最为直观的,如果查询成功,查询的结果直接保存在当 ThinkPHP3.2.3完全开发手册 - 207 -© 本文档使用 看云 构建 前的数据对象中,在进行下一次查询操作之前,我们都可以提取,例如获取查询 的结果数据: echo $User->name; echo $User->email; 如果要查询数据集,可以直接使用: // 查找主键为1、3、8的多个数据 $userList = $User->select('1,3,8'); 更新记录 在完成查询后,可以直接修改数据对象然后保存到数据库。 $User->find(1); // 查找主键为1的数据 $User->name = 'TOPThink'; // 修改数据对象 $User->save(); // 保存当前数据对象 上面这种方式仅仅是示例,不代表保存操作之前一定要先查询。因为下面的方式 其实是等效的: $User->id = 1; $User->name = 'TOPThink'; // 修改数据对象 $User->save(); // 保存当前数据对象 删除记录 可以删除当前查询的数据对象 $User->find(2); $User->delete(); // 删除当前的数据对象 ThinkPHP3.2.3完全开发手册 - 208 -© 本文档使用 看云 构建 或者直接根据主键进行删除 $User->delete(8); // 删除主键为8的数据 $User->delete('5,6'); // 删除主键为5、6的多个数据 字段映射 ThinkPHP的字段映射功能可以让你在表单中隐藏真正的数据表字段,而不用担 心放弃自动创建表单对象的功能,假设我们的User表里面有username和email 字段,我们需要映射成另外的字段,定义方式如下: namespace Home\Model; use Think\Model; Class UserModel extends Model{ protected $_map = array( 'name' =>'username', // 把表单中name映射到数据表的usernam e字段 'mail' =>'email', // 把表单中的mail映射到数据表的email字段 ); } 这样,在表单里面就可以直接使用name和mail名称作为表单数据提交了。我们 使用 create 方法创建数据对象的时候,会自动转换成定义的实际数据表字段。 字段映射还可以支持对主键的映射。 使用字段映射后,默认不会对读取的数据会自动处理, ThinkPHP3.2.3完全开发手册 - 209 -© 本文档使用 看云 构建 // 实例化User模型 $User = D('User'); $data = $User->find(3); dump($data); 输出结果类似: array(size=4) 'id' => int 3 'username'=> string 'thinkphp'(length=8) 'email' => string 'thinkphp@gmail.com' (length=18) 'status' => int 1 这个时候取出的data数据包含的是实际的username和email字段。 如果我们需要在数据获取的时候自动处理的话,设置开启 READ_DATA_MAP 参数, 'READ_DATA_MAP'=>true 这个时候,输出结果类似: array(size=4) 'id' => int 3 'name' => string 'thinkphp'(length=8) 'mail' => string 'thinkphp@gmail.com' (length=18) 'status' => int 1 或者直接使用 parseFieldsMap 方法进行转换处理,例如: ThinkPHP3.2.3完全开发手册 - 210 -© 本文档使用 看云 构建 // 实例化User模型 $User = D('User'); $data = $User->find(3); $data = $User->parseFieldsMap($data); 通过上面的两种方式后,无论是find还是select方法读取后的data数据中就包含 了name和mail字段数据了,而不再有username和email字段数据了。 查询语言 查询方式 ThinkPHP可以支持直接使用字符串作为查询条件,但是大多数情况推荐使用数 组或者对象来作为查询条件,因为会更加安全。 使用字符串作为查询条件 这是最传统的方式,但是安全性不高,例如: $User = M("User"); // 实例化User对象 $User->where('type=1 AND status=1')->select(); 最后生成的SQL语句是 SELECT * FROM think_user WHERE type=1 AND status=1 采用字符串查询的时候,我们可以配合使用字符串条件的安全预处理机制。 ThinkPHP3.2.3完全开发手册 - 211 -© 本文档使用 看云 构建 使用数组作为查询条件 这种方式是最常用的查询方式,例如: $User = M("User"); // 实例化User对象 $condition['name'] = 'thinkphp'; $condition['status'] = 1; // 把查询条件传入查询方法 $User->where($condition)->select(); 最后生成的SQL语句是 SELECT * FROM think_user WHERE `name`='thinkphp' AND status =1 如果进行多字段查询,那么字段之间的默认逻辑关系是 逻辑与 AND,但是用下 面的规则可以更改默认的逻辑判断,通过使用 _logic 定义查询逻辑: $User = M("User"); // 实例化User对象 $condition['name'] = 'thinkphp'; $condition['account'] = 'thinkphp'; $condition['_logic'] = 'OR'; // 把查询条件传入查询方法 $User->where($condition)->select(); 最后生成的SQL语句是 SELECT * FROM think_user WHERE `name`='thinkphp' OR `account `='thinkphp' 使用对象方式来查询 ThinkPHP3.2.3完全开发手册 - 212 -© 本文档使用 看云 构建 这里以stdClass内置对象为例: $User = M("User"); // 实例化User对象 // 定义查询条件 $condition = new stdClass(); $condition->name = 'thinkphp'; $condition->status= 1; $User->where($condition)->select(); 最后生成的SQL语句和上面一样 SELECT * FROM think_user WHERE `name`='thinkphp' AND status =1 使用对象方式查询和使用数组查询的效果是相同的,并且是可以互换的,大多数 情况下,我们建议采用数组方式更加高效。 在使用数组和对象方式查询使用数组和对象方式查询的时候,如果传入了不存在的查询字段是会被自动 过滤的,例如: $User = M("User"); // 实例化User对象 $condition['name'] = 'thinkphp'; $condition['status'] = 1; $condition['test'] = 'test'; // 把查询条件传入查询方法 $User->where($condition)->select(); 因为数据库的test字段是不存在的,所以系统会自动检测并过滤掉 $condition['test'] = 'test' 这一查询条件。 如果是3.2.2版本以上,当开启调试模式的话,则会抛出异常,显示: 错误的查询条件 。 ThinkPHP3.2.3完全开发手册 - 213 -© 本文档使用 看云 构建 表达式查询 上面的查询条件仅仅是一个简单的相等判断,可以使用查询表达式支持更多的 SQL查询语法,也是ThinkPHP查询语言的精髓,查询表达式的使用格式: $map['字段名'] = array('表达式','查询条件'); 表达式不分大小写,支持的查询表达式有下面几种,分别表示的含义是: 表达式表达式 含义含义 协助记忆协助记忆 EQ 等于(=) equal NEQ 不等于(<>) not equal GT 大于(>) greater EGT 大于等于(>=) equal or greater LT 小于(<) less than ELT 小于等于(<=) equal or less than LIKE 模糊查询 [NOT] BETWEEN (不在)区间查询 [NOT] IN (不在)IN 查询 EXP 表达式查询,支持SQL语法 expression 表达式查询的用法示例如下: EQ :等于(=) 例如: ThinkPHP3.2.3完全开发手册 - 214 -© 本文档使用 看云 构建 $map['id'] = array('eq',100); 和下面的查询等效 $map['id'] = 100; 表示的查询条件就是 id = 100 NEQ: 不等于(<>) 例如: $map['id'] = array('neq',100); 表示的查询条件就是 id <> 100 GT:大于(>) 例如: $map['id'] = array('gt',100); 表示的查询条件就是 id > 100 EGT:大于等于(>=) 例如: $map['id'] = array('egt',100); 表示的查询条件就是 id >= 100 ThinkPHP3.2.3完全开发手册 - 215 -© 本文档使用 看云 构建 LT:小于(<) 例如: $map['id'] = array('lt',100); 表示的查询条件就是 id < 100 ELT: 小于等于(<=) 例如: $map['id'] = array('elt',100); 表示的查询条件就是 id <= 100 [NOT] LIKE: 同sql的LIKE 例如: $map['name'] = array('like','thinkphp%'); 查询条件就变成 name like 'thinkphp%' 如果配置了DB_LIKE_FIELDS参数 的话,某些字段也会自动进行模糊查询。例如设置了: 'DB_LIKE_FIELDS'=>'title|content' 的话,使用 $map['title'] = 'thinkphp'; 查询条件就会变成 title like '%thinkphp%' 支持数组方式,例如 ThinkPHP3.2.3完全开发手册 - 216 -© 本文档使用 看云 构建 $map['a'] =array('like',array('%thinkphp%','%tp'),'OR'); $map['b'] =array('notlike',array('%thinkphp%','%tp'),'AND'); 生成的查询条件就是: (a like '%thinkphp%' OR a like '%tp') AND (b not like '%thinkphp%' AND b not like '%tp') [NOT] BETWEEN :同sql的[not] between 查询条件支持字符串或者数组,例如: $map['id'] = array('between','1,8'); 和下面的等效: $map['id'] = array('between',array('1','8')); 查询条件就变成 id BETWEEN 1 AND 8 [NOT] IN: 同sql的[not] in 查询条件支持字符串或者数组,例如: $map['id'] = array('not in','1,5,8'); 和下面的等效: $map['id'] = array('not in',array('1','5','8')); 查询条件就变成 id NOT IN (1,5, 8) ThinkPHP3.2.3完全开发手册 - 217 -© 本文档使用 看云 构建 EXP:表达式 支持更复杂的查询情况 例如: $map['id'] = array('in','1,3,8'); 可以改成: $map['id'] = array('exp',' IN (1,3,8) '); exp查询的条件不会被当成字符串,所以后面的查询条件可以使用任何SQL支持 的语法,包括使用函数和字段名称。查询表达式不仅可用于查询条件,也可以用 于数据更新,例如: $User = M("User"); // 实例化User对象 // 要修改的数据对象属性赋值 $data['name'] = 'ThinkPHP'; $data['score'] = array('exp','score+1');// 用户的积分加1 $User->where('id=5')->save($data); // 根据条件保存修改的数据 快捷查询 快捷查询方式是一种多字段查询的简化写法,可以进一步简化查询条件的写法, 在多个字段之间用||分割表示OR查询,用&&分割表示AND查询,可以实现下面 的查询,例如: 不同字段相同的查询条件 ThinkPHP3.2.3完全开发手册 - 218 -© 本文档使用 看云 构建 $User = M("User"); // 实例化User对象 $map['name|title'] = 'thinkphp'; // 把查询条件传入查询方法 $User->where($map)->select(); 上面的查询其实可以等效于 $User = M("User"); // 实例化User对象 $map['name'] = 'thinkphp'; $map['title'] = 'thinkphp'; $map['_logic'] = 'OR'; // 把查询条件传入查询方法 $User->where($map)->select(); 查询条件就变成 name= 'thinkphp' OR title = 'thinkphp' 不同字段不同的查询条件 $User = M("User"); // 实例化User对象 $map['status&title'] =array('1','thinkphp','_multi'=>true); // 把查询条件传入查询方法 $User->where($map)->select(); 上面的查询等效于: $User = M("User"); // 实例化User对象 $map['status'] = 1; $map['title'] = 'thinkphp'; // 把查询条件传入查询方法 $User->where($map)->select(); '_multi'=>true 必须加在数组的最后,表示当前是多条件匹配,这样查询条件 ThinkPHP3.2.3完全开发手册 - 219 -© 本文档使用 看云 构建 就变成 status= 1 AND title = 'thinkphp' ,查询字段支持更多的,例如: $map['status&score&title'] =array('1',array('gt','0'),'thinkphp','_multi '=>true); 等效于: $map['status'] = 1; $map['score'] = array('gt',0); $map['title'] = 'thinkphp'; 查询条件就变成 status= 1 AND score >0 AND title = 'thinkphp' 注意:快捷查询方式中“|”和“&”不能同时使用。 区间查询 ThinkPHP支持对某个字段的区间查询,例如: $map['id'] = array(array('gt',1),array('lt',10)) ; 得到的查询条件是: ( id > 1) AND ( id < 10) $map['id'] = array(array('gt',3),array('lt',10), 'or') ; 得到的查询条件是: ( id > 3) OR ( id < 10) ThinkPHP3.2.3完全开发手册 - 220 -© 本文档使用 看云 构建 $map['id'] = array(array('neq',6),array('gt',3),'and'); 得到的查询条件是: ( id != 6) AND ( id > 3) 最后一个可以是AND、 OR或者 XOR运算符,如果不写,默认是AND运 算。 区间查询的条件可以支持普通查询的所有表达式,也就是说类似LIKE、GT和 EXP这样的表达式都可以支持。另外区间查询还可以支持更多的条件,只要是针 对一个字段的条件都可以写到一起,例如: $map['name'] = array(array('like','%a%'), array('like','%b%'), array('lik e','%c%'), 'ThinkPHP','or'); 最后的查询条件是: ( name LIKE '%a%') OR ( name LIKE '%b%') OR ( name LIKE '%c%') O R ( name = 'ThinkPHP') 组合查询 组合查询的主体还是采用数组方式查询,只是加入了一些特殊的查询支持,包括 字符串模式查询( _string )、复合查询( _complex )、请求字符串查询( _query ),混合查询中的特殊查询每次查询只能定义一个,由于采用数组的索 引方式,索引相同的特殊查询会被覆盖。 字符串模式查询 数组条件可以和字符串条件(采用_string 作为查询条件)混合使用,例如: ThinkPHP3.2.3完全开发手册 - 221 -© 本文档使用 看云 构建 $User = M("User"); // 实例化User对象 $map['id'] = array('neq',1); $map['name'] = 'ok'; $map['_string'] = 'status=1 AND score>10'; $User->where($map)->select(); 最后得到的查询条件就成了: ( `id` != 1 ) AND ( `name` = 'ok' ) AND ( status=1 AND score>10 ) 请求字符串查询方式 请求字符串查询是一种类似于URL传参的方式,可以支持简单的条件相等判断。 $map['id'] = array('gt','100'); $map['_query'] = 'status=1&score=100&_logic=or'; 得到的查询条件是: `id`>100 AND (`status` = '1' OR `score` = '100') 复合查询 复合查询相当于封装了一个新的查询条件,然后并入原来的查询条件之中,所以 可以完成比较复杂的查询条件组装。 例如: $where['name'] = array('like', '%thinkphp%'); $where['title'] = array('like','%thinkphp%'); $where['_logic'] = 'or'; $map['_complex'] = $where; $map['id'] = array('gt',1); ThinkPHP3.2.3完全开发手册 - 222 -© 本文档使用 看云 构建 查询条件是 ( id > 1) AND ( ( name like '%thinkphp%') OR ( title like '%thinkphp %') ) 复合查询使用了_complex作为子查询条件来定义,配合之前的查询方式,可以 非常灵活的制定更加复杂的查询条件。 很多查询方式可以相互转换,例如上面 的查询条件可以改成: $where['id'] = array('gt',1); $where['_string'] = ' (name like "%thinkphp%") OR ( title like "%th inkphp") '; 最后生成的SQL语句是一致的。 统计查询 在应用中我们经常会用到一些统计数据,例如当前所有(或者满足某些条件)的 用户数、所有用户的最大积分、用户的平均成绩等等,ThinkPHP为这些统计操 作提供了一系列的内置方法,包括: 方法方法 说明说明 Count 统计数量,参数是要统计的字段名(可选) Max 获取最大值,参数是要统计的字段名(必须) Min 获取最小值,参数是要统计的字段名(必须) Avg 获取平均值,参数是要统计的字段名(必须) Sum 获取总分,参数是要统计的字段名(必须) ThinkPHP3.2.3完全开发手册 - 223 -© 本文档使用 看云 构建 用法示例: $User = M("User"); // 实例化User对象 获取用户数: $userCount = $User->count(); 或者根据字段统计: $userCount = $User->count("id"); 获取用户的最大积分: $maxScore = $User->max('score'); 获取积分大于0的用户的最小积分: $minScore = $User->where('score>0')->min('score'); 获取用户的平均积分: $avgScore = $User->avg('score'); 统计用户的总成绩: $sumScore = $User->sum('score'); 并且所有的统计查询均支持连贯操作的使用。 ThinkPHP3.2.3完全开发手册 - 224 -© 本文档使用 看云 构建 SQL查询 ThinkPHP内置的ORM和ActiveRecord模式实现了方便的数据存取操作,而 且新版增加的连贯操作功能更是让这个数据操作更加清晰,但是ThinkPHP仍然 保留了原生的SQL查询和执行操作支持,为了满足复杂查询的需要和一些特殊 的数据操作,SQL查询的返回值因为是直接返回的Db类的查询结果,没有做任 何的处理。 主要包括下面两个方法: QUERY方法 query方法用于执行SQL查询操作,如果数据非法或者查询错误则返回false, 否则返回查询结果数据集(同select方法)。 使用示例: $Model = new \Think\Model() // 实例化一个model对象 没有对应任何 数据表 $Model->query("select * from think_user where status=1"); 如果你当前采用了分布式数据库,并且设置了读写分离的话,query方法始 终是在读服务器执行,因此query方法对应的都是读操作,而不管你的SQL 语句是什么。 可以在query方法中使用表名的简化写法,便于动态更改表前缀,例如: ThinkPHP3.2.3完全开发手册 - 225 -© 本文档使用 看云 构建 $Model = new \Think\Model() // 实例化一个model对象 没有对应任何 数据表 $Model->query("select * from __PREFIX__user where status=1"); // 3.2.2版本以上还可以直接使用 $Model->query("select * from __USER__ where status=1"); 和上面的写法等效,会自动读取当前设置的表前缀。 EXECUTE方法 execute用于更新和写入数据的sql操作,如果数据非法或者查询错误则返回 false ,否则返回影响的记录数。 使用示例: $Model = new \Think\Model() // 实例化一个model对象 没有对应任何 数据表 $Model->execute("update think_user set name='thinkPHP' where status=1"); 如果你当前采用了分布式数据库,并且设置了读写分离的话,execute方法 始终是在写服务器执行,因此execute方法对应的都是写操作,而不管你的 SQL语句是什么。 也可以在execute方法中使用表名的简化写法,便于动态更改表前缀,例如: ThinkPHP3.2.3完全开发手册 - 226 -© 本文档使用 看云 构建 $Model = new \Think\Model() // 实例化一个model对象 没有对应任何 数据表 $Model->execute("update __PREFIX__user set name='thinkPHP' w here status=1"); // 3.2.2版本以上还可以直接使用 $Model->execute("update __USER__ set name='thinkPHP' where s tatus=1"); 和上面的写法等效,会自动读取当前设置的表前缀。 动态查询 借助PHP5语言的特性,ThinkPHP实现了动态查询,核心模型的动态查询方法 包括下面几种: 方法名方法名 说明说明 举例举例 getBy 根据字段的值查询数据 例 如,getByName,getByEmail getFieldBy 根据字段查询并返回某个 字段的值 例如,getFieldByName getBy动态查询 该查询方式针对数据表的字段进行查询。例如,User对象拥有 id,name,email,address 等属性,那么我们就可以使用下面的查询方法来直接根 据某个属性来查询符合条件的记录。 ThinkPHP3.2.3完全开发手册 - 227 -© 本文档使用 看云 构建 $user = $User->getByName('liu21st'); $user = $User->getByEmail('liu21st@gmail.com'); $user = $User->getByAddress('中国深圳'); 暂时不支持多数据字段的动态查询方法,请使用find方法和select方法进行查 询。 getFieldBy动态查询 针对某个字段查询并返回某个字段的值,例如 $userId = $User->getFieldByName('liu21st','id'); 表示根据用户的name获取用户的id值。 子查询 从3.0版本开始新增了子查询支持,有两种使用方式: 1、使用select方法 当select方法的参数为false的时候,表示不进行查询只是 返回构建SQL,例如: // 首先构造子查询SQL $subQuery = $model->field('id,name')->table('tablename')->group ('field')->where($where)->order('status')->select(false); 当select方法传入false参数的时候,表示不执行当前查询,而只是生成查询 SQL。 2、使用buildSql方法 ThinkPHP3.2.3完全开发手册 - 228 -© 本文档使用 看云 构建 $subQuery = $model->field('id,name')->table('tablename')->group ('field')->where($where)->order('status')->buildSql(); 调用buildSql方法后不会进行实际的查询操作,而只是生成该次查询的SQL语句 (为了避免混淆,会在SQL两边加上括号),然后我们直接在后续的查询中直 接调用。 // 利用子查询进行查询 $model->table($subQuery.' a')->where()->order()->select() 构造的子查询SQL可用于ThinkPHP的连贯操作方法,例如table where等。 自动验证 自动验证是ThinkPHP模型层提供的一种数据验证方法,可以在使用create创 建数据对象的时候自动进行数据验证。 验证规则 数据验证可以进行数据类型、业务规则、安全判断等方面的验证操作。 数据验证有两种方式: 1. 静态方式:在模型类里面通过$_validate属性定义验证规则。 2. 动态方式:使用模型类的validate方法动态创建自动验证规则。 无论是什么方式,验证规则的定义是统一的规则,定义格式为: ThinkPHP3.2.3完全开发手册 - 229 -© 本文档使用 看云 构建 array( array(验证字段1,验证规则,错误提示,[验证条件,附加规则,验证时间]), array(验证字段2,验证规则,错误提示,[验证条件,附加规则,验证时间]), ...... ); 说明 验证字段 (必须) 需要验证的表单字段名称,这个字段不一定是数据库字段,也可以是表单的一些 辅助字段,例如确认密码和验证码等等。有个别验证规则和字段无关的情况下, 验证字段是可以随意设置的,例如expire有效期规则是和表单字段无关的。如果 定义了字段映射的话,这里的验证字段名称应该是实际的数据表字段而不是表单 字段。 验证规则 (必须) 要进行验证的规则,需要结合附加规则,如果在使用正则验证的附加规则情况 下,系统还内置了一些常用正则验证的规则,可以直接作为验证规则使用,包 括:require 字段必须、email 邮箱、url URL地址、currency 货币、number 数字。 提示信息 (必须) 用于验证失败后的提示信息定义 验证条件 (可选) 包含下面几种情况: self::EXISTS_VALIDATE 或者0 存在字段就验证(默认) self::MUST_VALIDATE 或者1 必须验证 self::VALUE_VALIDATE或者2 值不为空的时候验证 ThinkPHP3.2.3完全开发手册 - 230 -© 本文档使用 看云 构建 附加规则 (可选) 配合验证规则使用,包括下面一些规则: 规则规则 说明说明 regex 正则验证,定义的验证规则是一个正则表达式(默认) function 函数验证,定义的验证规则是一个函数名 callback 方法验证,定义的验证规则是当前模型类的一个方法 confirm 验证表单中的两个字段是否相同,定义的验证规则是一个 字段名 equal 验证是否等于某个值,该值由前面的验证规则定义 notequal 验证是否不等于某个值,该值由前面的验证规则定义 (3.1.2版本新增) in 验证是否在某个范围内,定义的验证规则可以是一个数组 或者逗号分割的字符串 notin 验证是否不在某个范围内,定义的验证规则可以是一个数 组或者逗号分割的字符串(3.1.2版本新增) length 验证长度,定义的验证规则可以是一个数字(表示固定长 度)或者数字范围(例如3,12 表示长度从3到12的范围) between 验证范围,定义的验证规则表示范围,可以使用字符串或 者数组,例如1,31或者array(1,31) notbetween 验证不在某个范围,定义的验证规则表示范围,可以使用 字符串或者数组(3.1.2版本新增) expire 验证是否在有效期,定义的验证规则表示时间范围,可以 到时间,例如可以使用 2012-1-15,2013-1-15 表示当前 提交有效期在2012-1-15到2013-1-15之间,也可以使用 时间戳定义 ThinkPHP3.2.3完全开发手册 - 231 -© 本文档使用 看云 构建 ip_allow 验证IP是否允许,定义的验证规则表示允许的IP地址列 表,用逗号分隔,例如201.12.2.5,201.12.2.6 ip_deny 验证IP是否禁止,定义的验证规则表示禁止的ip地址列 表,用逗号分隔,例如201.12.2.5,201.12.2.6 unique 验证是否唯一,系统会根据字段目前的值查询数据库来判 断是否存在相同的值,当表单数据中包含主键字段时 unique不可用于判断主键字段本身 规则规则 说明说明 验证时间(可选) self::MODEL_INSERT或者1新增数据时候验证 self::MODEL_UPDATE或者2编辑数据时候验证 self::MODEL_BOTH或者3全部情况下验证(默认) 这里的验证时间需要注意,并非只有这三种情况,你可以根据业务需要增加其他 的验证时间。 静态定义 在模型类里面预先定义好该模型的自动验证规则,我们称为静态定义。 举例说明,我们在模型类里面定义了 $_validate 属性如下: ThinkPHP3.2.3完全开发手册 - 232 -© 本文档使用 看云 构建 namespace Home\Model; use Think\Model; class UserModel extends Model{ protected $_validate = array( array('verify','require','验证码必须!'), //默认情况下用正则进行验证 array('name','','帐号名称已经存在!',0,'unique',1), // 在新增的时候验 证name字段是否唯一 array('value',array(1,2,3),'值的范围不正确!',2,'in'), // 当值不为空的 时候判断是否在一个范围内 array('repassword','password','确认密码不正确',0,'confirm'), // 验证 确认密码是否和密码一致 array('password','checkPwd','密码格式不正确',0,'function'), // 自定 义函数验证密码格式 ); } 定义好验证规则后,就可以在使用create方法创建数据对象的时候自动调用: $User = D("User"); // 实例化User对象 if (!$User->create()){ // 如果创建失败 表示验证没有通过 输出错误提示信息 exit($User->getError()); }else{ // 验证通过 可以进行其他数据操作 } 在进行自动验证的时候,系统会对定义好的验证规则进行依次验证。如果某一条 验证规则没有通过,则会报错,getError方法返回的错误信息(字符串)就是 对应字段的验证规则里面的错误提示信息。 静态定义方式因为必须定义模型类,所以只能用D函数实例化模型 默认情况下,create方法是对表单提交的POST数据进行自动验证,如果你的数 ThinkPHP3.2.3完全开发手册 - 233 -© 本文档使用 看云 构建 据来源不是表单post,仍然也可以进行自动验证,方法改进如下: $User = D("User"); // 实例化User对象 $data = getData(); // 通过getData方法获取数据源的(数组)数据 if (!$User->create($data)){ // 对data数据进行验证 exit($User->getError()); }else{ // 验证通过 可以进行其他数据操作 } 一般情况下,create方法会自动判断当前是新增数据还是编辑数据(主要是通 过表单的隐藏数据添加主键信息),你也可以明确指定当前创建的数据对象是新 增还是编辑,例如: $User = D("User"); // 实例化User对象 if (!$User->create($_POST,1)){ // 指定新增数据 // 如果创建失败 表示验证没有通过 输出错误提示信息 exit($User->getError()); }else{ // 验证通过 可以进行其他数据操作 } create方法的第二个参数就用于指定自动验证规则中的验证时间,也就是说 create方法的自动验证只会验证符合验证时间的验证规则。 我们在上面提到这里的验证时间并非只有这几种情况,你可以根据业务需要增加 其他的验证时间,例如,你可以给登录操作专门指定验证时间为4。我们定义验 证规则如下: ThinkPHP3.2.3完全开发手册 - 234 -© 本文档使用 看云 构建 namespace Home\Model; use Think\Model; class UserModel extends Model{ protected $_validate = array( array('verify','require','验证码必须!'), // 都有时间都验证 array('name','checkName','帐号错误!',1,'function',4), // 只在登录 时候验证 array('password','checkPwd','密码错误!',1,'function',4), // 只在登 录时候验证 ); } 那么,我们就可以在登录的时候使用 $User = D("User"); // 实例化User对象 if (!$User->create($_POST,4)){ // 登录验证数据 // 验证没有通过 输出错误提示信息 exit($User->getError()); }else{ // 验证通过 执行登录操作 } 动态验证 如果采用动态验证的方式,就比较灵活,可以根据不同的需要,在操作同一个模 型的时候使用不同的验证规则,例如上面的静态验证方式可以改为: ThinkPHP3.2.3完全开发手册 - 235 -© 本文档使用 看云 构建 $rules = array( array('verify','require','验证码必须!'), //默认情况下用正则进行验证 array('name','','帐号名称已经存在!',0,'unique',1), // 在新增的时候验 证name字段是否唯一 array('value',array(1,2,3),'值的范围不正确!',2,'in'), // 当值不为空的 时候判断是否在一个范围内 array('repassword','password','确认密码不正确',0,'confirm'), // 验证 确认密码是否和密码一致 array('password','checkPwd','密码格式不正确',0,'function'), // 自定 义函数验证密码格式 ); $User = M("User"); // 实例化User对象 if (!$User->validate($rules)->create()){ // 如果创建失败 表示验证没有通过 输出错误提示信息 exit($User->getError()); }else{ // 验证通过 可以进行其他数据操作 } 动态验证不依赖模型类的定义,所以通常用M函数实例化模型就可以 错误信息多语言支持 如果你希望支持多语言的错误信息提示,那么可以在验证规则里面如下定义: protected $_validate = array( array('verify','require','{%VERIFY_CODE_MUST}'), array('name','','{%ACCOUNT_EXISTS}',0,'unique',1), ); 其中 VERIFY_CODE_MUST 和 ACCOUNT_EXISTS 是我们在语言包里面定义 的多语言变量。 ThinkPHP3.2.3完全开发手册 - 236 -© 本文档使用 看云 构建 如果是采用动态验证方式,则比较简单,直接在定义验证规则的时候使用L方法 即可,例如: $rules = array( array('verify','require',L('VERIFY_CODE_MUST')), array('name','',L('ACCOUNT_EXISTS'),0,'unique',1), ); 批量验证 系统支持数据的批量验证功能,只需要在模型类里面设置patchValidate属性为 true( 默认为false), protected $patchValidate = true; 设置批处理验证后, getError() 方法返回的错误信息是一个数组,返回格式 是: array("字段名1"=>"错误提示1","字段名2"=>"错误提示2"... ) 前端可以根据需要需要自行处理,例如转换成json格式返回: $User = D("User"); // 实例化User对象 if (!$User->create()){ // 如果创建失败 表示验证没有通过 输出错误提示信息 $this->ajaxReturn($User->getError()); }else{ // 验证通过 可以进行其他数据操作 } ThinkPHP3.2.3完全开发手册 - 237 -© 本文档使用 看云 构建 自动完成 自动完成是ThinkPHP提供用来完成数据自动处理和过滤的方法,使用create 方法创建数据对象的时候会自动完成数据处理。 因此,在ThinkPHP使用create方法来创建数据对象是更加安全的方式,而不 是直接通过add或者save方法实现数据写入。 规则定义 自动完成通常用来完成默认字段写入,安全字段过滤以及业务逻辑的自动处理 等,和自动验证的定义方式类似,自动完成的定义也支持静态定义和动态定义两 种方式。 1. 静态方式:在模型类里面通过$_auto属性定义处理规则。 2. 动态方式:使用模型类的auto方法动态创建自动处理规则。 两种方式的定义规则都采用: array( array(完成字段1,完成规则,[完成条件,附加规则]), array(完成字段2,完成规则,[完成条件,附加规则]), ...... ); 说明 完成字段(必须) 需要进行处理的数据表实际字段名称。 完成规则(必须) ThinkPHP3.2.3完全开发手册 - 238 -© 本文档使用 看云 构建 需要处理的规则,配合附加规则完成。 完成时间(可选) 设置自动完成的时间,包括: 设置设置 说明说明 self::MODEL_INSERT或者1 新增数据的时候处理(默认) self::MODEL_UPDATE或者2 更新数据的时候处理 self::MODEL_BOTH或者3 所有情况都进行处理 附加规则(可选) 包括: 规则规则 说明说明 function 使用函数,表示填充的内容是一个函数名 callback 回调方法 ,表示填充的内容是一个当前模型的方法 field 用其它字段填充,表示填充的内容是一个其他字段的值 string 字符串(默认方式) ignore 为空则忽略(3.1.2新增) 静态定义 预先在模型类里面定义好自动完成的规则,我们称之为静态定义。例如,我们在 模型类定义 _auto 属性: ThinkPHP3.2.3完全开发手册 - 239 -© 本文档使用 看云 构建 namespace Home\Model; use Think\Model; class UserModel extends Model{ protected $_auto = array ( array('status','1'), // 新增的时候把status字段设置为1 array('password','md5',3,'function') , // 对password字段在新增 和编辑的时候使md5函数处理 array('name','getName',3,'callback'), // 对name字段在新增和编 辑的时候回调getName方法 array('update_time','time',2,'function'), // 对update_time字段在 更新的时候写入当前时间戳 ); } 然后,就可以在使用create方法创建数据对象的时候自动处理: $User = D("User"); // 实例化User对象 if (!$User->create()){ // 创建数据对象 // 如果创建失败 表示验证没有通过 输出错误提示信息 exit($User->getError()); }else{ // 验证通过 写入新增数据 $User->add(); } 如果你没有定义任何自动验证规则的话,则不需要判断create方法的返回值: $User = D("User"); // 实例化User对象 $User->create(); // 生成数据对象 $User->add(); // 新增用户数据 或者更简单的使用: ThinkPHP3.2.3完全开发手册 - 240 -© 本文档使用 看云 构建 $User = D("User"); // 实例化User对象 $User->create(); // 生成数据对象 $User->add(); // 写入数据 create方法默认情况下是根据表单提交的post数据生成数据对象,我们也可以 根据其他的数据源来生成数据对象,你也可以明确指定当前创建的数据对象自动 处理的时间是新增还是编辑数据,例如: $User = D("User"); // 实例化User对象 $userData = getUserData(); // 通过方法获取用户数据 $User->create($userData,2); // 根据userData数据创建数据对象,并指 定为更新数据 $User->add(); create方法的第二个参数就用于指定自动完成规则中的完成时间,也就是说 create方法的自动处理规则只会处理符合完成时间的自动完成规则。 create方 法在创建数据的时候,已经自动过滤了非数据表字段数据信息,因此不需要担心 表单会提交其他的非法字段信息而导致数据对象写入出错,甚至还可以自动过滤 不希望用户在表单提交的字段信息(详见字段合法性过滤)。 3.1.2版本开始新增了ignore完成规则,这一规则表示某个字段如果留空的话则 忽略,通常可用于修改用户资料时候密码的输入,定义如下: array('password','',2,'ignore') 表示password字段编辑的时候留空则忽略。 动态完成 除了静态定义之外,我们也可以采用动态完成的方式来解决不同的处理规则。 ThinkPHP3.2.3完全开发手册 - 241 -© 本文档使用 看云 构建 $rules = array ( array('status','1'), // 新增的时候把status字段设置为1 array('password','md5',3,'function') , // 对password字段在新增和编 辑的时候使md5函数处理 array('update_time','time',2,'function'), // 对update_time字段在更 新的时候写入当前时间戳 ); $User = M('User'); $User->auto($rules)->create(); $User->add(); 修改数据对象 在使用create方法创建好数据对象之后,此时的数据对象保存在内存中,因此 仍然可以操作数据对象,包括修改或者增加数据对象的值,例如: $User = D("User"); // 实例化User对象 $User->create(); // 生成数据对象 $User->status = 2; // 修改数据对象的status属性 $User->register_time = NOW_TIME; // 增加register_time属性 $User->add(); // 新增用户数据 一旦调用了add方法(或者save方法),创建在内存中的数据对象就会失效,如 果希望创建好的数据对象在后面的数据处理中再次调用,可以保存数据对象先, 例如: $User = D("User"); // 实例化User对象 $data = $User->create(); // 保存生成的数据对象 $User->add(); 不过要记得,如果你修改了内存中的数据对象并不会自动更新保存的数据对象, 因此下面的用法是错误的: ThinkPHP3.2.3完全开发手册 - 242 -© 本文档使用 看云 构建 $User = D("User"); // 实例化User对象 $data = $User->create(); // 保存生成的数据对象 $User->status = 2; // 修改数据对象的status属性 $User->register_time = NOW_TIME; // 增加register_time属性 $User->add($data); 上面的代码我们修改了数据对象,但是仍然写入的是之前保存的数据对象,因此 对数据对象的更改操作将会无效。 参数绑定 参数绑定是指绑定一个参数到预处理的SQL语句中的对应命名占位符或问号占 位符指定的变量,并且可以提高SQL处理的效率。 手动绑定 参数手动绑定需要调用连贯操作的bind方法,例如: $Model = M('User'); $where['name'] = ':name'; $list = $Model->where($where)->bind(':name',I('name'))->select(); 目前不支持 ?方式 进行占位符,统一使用 :var 方式进行占位符,驱动内部会 自动进行处理。 参数绑定的参数可以是条件或者要data数据中的参数,CURD操作均可以支 持参数绑定bind方法。 可以支持指定绑定变量的类型参数,例如: ThinkPHP3.2.3完全开发手册 - 243 -© 本文档使用 看云 构建 $Model = M('User'); $where['id'] = ':id'; $list = $Model->where($where)->bind(':id',I('id'),\PDO::PARAM_IN T)->select(); 也可以批量绑定,例如: $Model = M('User'); $where['id'] = ':id'; $where['name'] = ':name'; $bind[':id'] = array(I('id'),\PDO::PARAM_INT); $bind[':name'] = array(I('name'),\PDO::PARAM_STR); $list = $Model->where($where)->bind($bind)->select(); 自动绑定 对于某些操作的情况(例如模型的写入和更新方法),采用了参数的自动绑定, 例如我们在使用 $Model = M('User'); $Model->name = 'thinkphp'; $Model->email = 'thinkphp@qq.com'; $Model->add(); 会自动对写入的数据进行参数绑定操作。其操作等效于: $Model = M('User'); $Model->name = ':name'; $Model->email = ':email'; $bind[':name'] = 'thinkphp'; $bind[':email'] = 'thinkphp@qq.com'; $Model->bind($bind)->add(); ThinkPHP3.2.3完全开发手册 - 244 -© 本文档使用 看云 构建 自动绑定不支持参数类型等额外设置,如果有必要请使用上面的手动绑定方 式。 虚拟模型 虚拟模型是指虽然是模型类,但并不会真正的操作数据库的模型。有些时候,我 们建立模型类但又不需要进行数据库操作,仅仅是借助模型类来封装一些业务逻 辑,那么可以借助虚拟模型来完成。虚拟模型不会自动连接数据库,因此也不会 自动检测数据表和字段信息,有两种方式可以定义虚拟模型: 继承Model类 namespace Home\Model; Class UserModel extends \Think\Model { Protected $autoCheckFields = false; } 设置autoCheckFields属性为false后,就会关闭字段信息的自动检测,因为 ThinkPHP采用的是惰性数据库连接,只要你不进行数据库查询操作,是不会连 接数据库的。 不继承Model类 namespace Home\Model; Class UserModel { } 这种方式下面自定义模型类就是一个单纯的业务逻辑类,不能再使用模型的 CURD操作方法,但是可以实例化其他的模型类进行相关操作,也可以在需要的 时候直接实例化Db类进行数据库操作。 ThinkPHP3.2.3完全开发手册 - 245 -© 本文档使用 看云 构建 模型分层 ThinkPHP支持模型的分层 ,除了Model层之外,我们可以项目的需要设计和 创建其他的模型层。 通常情况下,不同的分层模型仍然是继承系统的\Think\Model类或其子类,所 以,其基本操作和Model类的操作是一致的。 例如在Home模块的设计中需要区分数据层、逻辑层、服务层等不同的模型层, 我们可以在模块目录下面创建 Model 、 Logic 和 Service 目录,把对用户表的 所有模型操作分成三层: 数据层:Home\Model\UserModel 用于定义数据相关的自动验证和自动 完成和数据存取接口 逻辑层:Home\Logic\UserLogic 用于定义用户相关的业务逻辑 服务层:Home\Service\UserService 用于定义用户相关的服务接口等 三个模型层的定义如下: Model类:Home\Model\UserModel.class.php namespace Home\Model; class UserModel extends \Think\Model{ } 实例化方法: D('User'); Logic类:Home\Logic\UserLogic.class.php ThinkPHP3.2.3完全开发手册 - 246 -© 本文档使用 看云 构建 namespace Home\Logic; class UserLogic extends \Think\Model{ } 实例化方法: D('User','Logic'); Api类:Home\Api\UserApi.class.php namespace Home\Api; class UserApi extends \Think\Model{ } 实例化方法: D('User','Api'); D方法默认操作的模型层由 DEFAULT_M_LAYER 参数配置,我们可以改变默 认操作的模型层为Logic层,例如: 'DEFAULT_M_LAYER' => 'Logic', // 默认的模型层名称 这样,当我们调用: $User = D('User'); 的时候其实是实例化的 UserLogic 类,而不是 UserModel 类。 视图模型 ThinkPHP3.2.3完全开发手册 - 247 -© 本文档使用 看云 构建 视图定义 视图通常是指数据库的视图,视图是一个虚拟表,其内容由查询定义。同真实的 表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以 存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且 在引用视图时动态生成。对其中所引用的基础表来说,视图的作用类似于筛选。 定义视图的筛选可以来自当前或其它数据库的一个或多个表,或者其它视图。分 布式查询也可用于定义使用多个异类源数据的视图。如果有几台不同的服务器分 别存储组织中不同地区的数据,而您需要将这些服务器上相似结构的数据组合起 来,这种方式就很有用。 视图在有些数据库下面并不被支持,但是ThinkPHP 模拟实现了数据库的视图,该功能可以用于多表联合查询。非常适合解 决HAS_ONEHAS_ONE 和 BELONGS_T OBELONGS_T O 类型的关联查询。 要定义视图模型,只需要继承 Think\Model\ViewModel ,然后设置 viewFields 属性即可。 例如下面的例子,我们定义了一个BlogView模型对象,其中包括了Blog模型的 id、name、title和User模型的name,以及Category模型的title字段,我们通 过创建BlogView模型来快速读取一个包含了User名称和类别名称的Blog记录 (集)。 ThinkPHP3.2.3完全开发手册 - 248 -© 本文档使用 看云 构建 namespace Home\Model; use Think\Model\ViewModel; class BlogViewModel extends ViewModel { public $viewFields = array( 'Blog'=>array('id','name','title'), 'Category'=>array('title'=>'category_name', '_on'=>'Blog.catego ry_id=Category.id'), 'User'=>array('name'=>'username', '_on'=>'Blog.user_id=User.id '), ); } 我们来解释一下定义的格式代表了什么。 $viewFields 属性表示视图模型包含的字段,每个元素定义了某个数据表或者 模型的字段。 例如: 'Blog'=>array('id','name','title'); 表示BlogView视图模型要包含Blog模型中的id、name和title字段属性,这个 其实很容易理解,就和数据库的视图要包含某个数据表的字段一样。而Blog相 当于是给Blog模型对应的数据表定义了一个别名。 默认情况下会根据定义的名称自动获取表名,如果希望指定数据表,可以使用: '_table'=>"test_user" // 或者使用简化定义(自动获取表前缀) // '_table'=>"__USER__" 如果希望给当前数据表定义另外的别名,可以使用 ThinkPHP3.2.3完全开发手册 - 249 -© 本文档使用 看云 构建 '_as'=>'myBlog' BlogView视图模式除了包含Blog模型之外,还包含了Category和User模型, 下面的定义: 'Category'=>array('title'=>'category_name'); 和上面类似,表示BlogView视图模型还要包含Category模型的title字段,因 为视图模型里面已经存在了一个title字段,所以我们通过 'title'=>'category_name' 把Category模型的title字段映射为 category_name 字段,如果有多个字段, 可以使用同样的方式添加。 可以通过_on来给视图模型定义关联查询条件,例如: '_on'=>'Blog.category_id=Category.id' 理解之后,User模型的定义方式同样也就很容易理解了。 Blog.categoryId=Category.id AND Blog.userId=User.id 最后,我们把视图模型的定义翻译成SQL语句就更加容易理解视图模型的原理 了。假设我们不带任何其他条件查询全部的字段,那么查询的SQL语句就是 ThinkPHP3.2.3完全开发手册 - 250 -© 本文档使用 看云 构建 Select Blog.id as id, Blog.name as name, Blog.title as title, Category.title as category_name, User.name as username from think_blog Blog JOIN think_category Category JOIN think_us er User where Blog.category_id=Category.id AND Blog.user_id=User.id 视图模型的定义并不需要先单独定义其中的模型类,系统会默认按照系统的规则 进行数据表的定位。如果Blog模型并没有定义,那么系统会自动根据当前模型 的表前缀和后缀来自动获取对应的数据表。也就是说,如果我们并没有定义 Blog模型类,那么上面的定义后,系统在进行视图模型的操作的时候会根据 Blog这个名称和当前的表前缀设置(假设为 Think_ )获取到对应的数据表可 能是 think_blog 。 ThinkPHP还可以支持视图模型的JOIN类型定义,我们可以把上面的视图定义 改成: public $viewFields = array( 'Blog'=>array('id','name','title','_type'=>'LEFT'), 'Category'=>array('title'=>'category_name','_on'=>'Category.id= Blog.category_id','_type'=>'RIGHT'), 'User'=>array('name'=>'username','_on'=>'User.id=Blog.user_id') , ); 需要注意的是,这里的_type定义对下一个表有效,因此要注意视图模型的定义 顺序。Blog模型的 ThinkPHP3.2.3完全开发手册 - 251 -© 本文档使用 看云 构建 '_type'=>'LEFT' 针对的是下一个模型Category而言,通过上面的定义,我们在查询的时候最终 生成的SQL语句就变成: Select Blog.id as id, Blog.name as name, Blog.title as title, Category.title as category_name, User.name as username from think_blog Blog LEFT JOIN think_category Category ON Blog. category_id=Category.id RIGHT JOIN think_user User ON Blog.use r_id=User.id 我们可以在试图模型里面定义特殊的字段,例如下面的例子定义了一个统计字段 'Category'=>array('title'=>'category_name','COUNT(Blog.id)'=>'co unt','_on'=>'Category.id=Blog.category_id'), 视图查询 接下来,我们就可以和使用普通模型一样对视图模型进行操作了 。 $Model = D("BlogView"); $Model->field('id,name,title,category_name,username')->where('id >10')->order('id desc')->select(); 看起来和普通的模型操作并没有什么大的区别,可以和使用普通模型一样进行查 询。如果发现查询的结果存在重复数据,还可以使用group方法来处理。 ThinkPHP3.2.3完全开发手册 - 252 -© 本文档使用 看云 构建 $Model->field('id,name,title,category_name,username')->order('id desc')->group('id')->select(); 我们可以看到,即使不定义视图模型,其实我们也可以通过方法来操作,但是显 然非常繁琐。 $Model = D("Blog"); $Model->table('think_blog Blog,think_category Category,think_use r User') ->field('Blog.id,Blog.name,Blog.title,Category.title as category_na me,User.name as username') ->order('Blog.id desc') ->where('Blog.category_id=Category.id AND Blog.user_id=User.id' ) ->select(); 而定义了视图模型之后,所有的字段会进行自动处理,添加表别名和字段别名, 从而简化了原来视图的复杂查询。如果不使用视图模型,也可以用连贯操作的 JOIN方法实现相同的功能。 关联模型 关联关系 通常我们所说的关联关系包括下面三种: 一对一关联 :ONE_TO_ONE,包括HAS_ONE 和 BELONGS_TO 一对多关联 :ONE_TO_MANY,包括HAS_MANY 和 BELONGS_TO 多对多关联 :MANY_TO_MANY ThinkPHP3.2.3完全开发手册 - 253 -© 本文档使用 看云 构建 关联关系必然有一个参照表,例如: 有一个员工档案管理系统项目,这个项目要包括下面的一些数据表:基本信 息表、员工档案表、部门表、项目组表、银行卡表(用来记录员工的银行卡 资料)。 这些数据表之间存在一定的关联关系,我们以员工基本信息表为参照来分析 和其他表之间的关联: 每个员工必然有对应的员工档案资料,所以属于HAS_ONE关联; 每个员工必须属于某个部门,所以属于BELONGS_TO关联; 每个员工可以有多个银行卡,但是每张银行卡只可能属于一个员工,因此属 于HAS_MANY关联; 每个员工可以同时在多个项目组,每个项目组同时有多个员工,因此属于 MANY_TO_MANY关联; 分析清楚数据表之前的关联关系后,我们才可以进行关联定义和关联操作。 关联定义 ThinkPHP可以很轻松的完成数据表的关联CURD操作,目前支持的关联关系包 括下面四种: HAS_ONEHAS_ONE 、BELONGS_T OBELONGS_T O 、HAS_M ANYHAS_M ANY和M ANY_T O_M ANYM ANY_T O_M ANY。 一个模型根据业务模型的复杂程度可以同时定义多个关联,不受限制,所有的关 联定义都统一在模型类的 $_link 成员变量里面定义,并且可以支持动态定义。 要支持关联操作,模型类必须继承 Think\Model\RelationModel 类,关联定 义的格式是: ThinkPHP3.2.3完全开发手册 - 254 -© 本文档使用 看云 构建 namespace Home\Model; use Think\Model\RelationModel; class UserModel extends RelationModel{ protected $_link = array( '关联1' => array( '关联属性1' => '定义', '关联属性N' => '定义', ), '关联2' => array( '关联属性1' => '定义', '关联属性N' => '定义', ), '关联3' => HAS_ONE, // 快捷定义 ... ); } 下面我们首先来分析下各个关联方式的定义: HAS_ONE HAS_ONE关联表示当前模型拥有一个子对象,例如,每个员工都有一个人事档 案。我们可以建立一个用户模型UserModel,并且添加如下关联定义: namespace Home\Model; use Think\Model\RelationModel; class UserModel extends RelationModel{ protected $_link = array( 'Profile'=> self::HAS_ONE, ); } 上面是最简单的方式,表示其遵循了系统内置的数据库规范,完整的定义方式 是: ThinkPHP3.2.3完全开发手册 - 255 -© 本文档使用 看云 构建 namespace Home\Model; use Think\Model\RelationModel; class UserModel extends RelationModel{ protected $_link = array( 'Profile'=>array( 'mapping_type' => self::HAS_ONE, 'class_name' => 'Profile', // 定义更多的关联属性 …… ), ); } 关联HAS_ONE支持的关联属性有: mapping_type :关联类型mapping_type :关联类型 这个在HAS_ONE 关联里面必须使用HAS_ONE 常量定义。 class_name :要关联的模型类名class_name :要关联的模型类名 例如,class_name 定义为Profile的话则表示和另外的Profile模型类关联,这 个Profile模型类是无需定义的,系统会自动定位到相关的数据表进行关联。 mapping_name :关联的映射名称,用于获取数据用mapping_name :关联的映射名称,用于获取数据用 该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。如果 mapping_name没有定义的话,会取class_name的定义作为 mapping_name。如果class_name也没有定义,则以数组的索引作为 mapping_name。 foreign_key : 关联的外键名称foreign_key : 关联的外键名称 外键的默认规则是当前数据对象名称_id,例如: UserModel对应的可能是表 think_user (注意:think只是一个表前缀,可以随意配置) 那么think_user ThinkPHP3.2.3完全开发手册 - 256 -© 本文档使用 看云 构建 表的外键默认为 user_id,如果不是,就必须在定义关联的时候显式定义 foreign_key 。 condition : 关联条件condition : 关联条件 关联查询的时候会自动带上外键的值,如果有额外的查询条件,可以通过定义关 联的condition属性。 mapping_fields : 关联要查询的字段mapping_fields : 关联要查询的字段 默认情况下,关联查询的关联数据是关联表的全部字段,如果只是需要查询个别 字段,可以定义关联的mapping_fields属性。 as_fields :直接把关联的字段值映射成数据对象中的某个字段as_fields :直接把关联的字段值映射成数据对象中的某个字段 这个特性是ONE_TO_ONE 关联特有的,可以直接把关联数据映射到数据对象 中,而不是作为一个关联数据。当关联数据的字段名和当前数据对象的字段名称 有冲突时,还可以使用映射定义。 BELONGS_TO Belongs_to 关联表示当前模型从属于另外一个父对象,例如每个用户都属于一 个部门。我们可以做如下关联定义。 'Dept' => self::BELONGS_TO 完整方式定义为: ThinkPHP3.2.3完全开发手册 - 257 -© 本文档使用 看云 构建 'Dept' => array( 'mapping_type' => self::BELONGS_TO, 'class_name' => 'Dept', 'foreign_key' => 'userId', 'mapping_name' => 'dept', // 定义更多的关联属性 …… ), 关联BELONGS_TO定义支持的关联属性有: 属性属性 描述描述 class_name 要关联的模型类名 mapping_name 关联的映射名称,用于获取数据用 该名称不要和当前模 型的字段有重复,否则会导致关联数据获取的冲突。 foreign_key 关联的外键名称 mapping_fields 关联要查询的字段 condition 关联条件 parent_key 自引用关联的关联字段 默认为parent_id 自引用关联是 一种比较特殊的关联,也就是关联表就是当前表。 as_fields 直接把关联的字段值映射成数据对象中的某个字段 HAS_MANY HAS_MANY 关联表示当前模型拥有多个子对象,例如每个用户有多篇文章, 我们可以这样来定义: 'Article' => self::HAS_MANY 完整定义方式为: ThinkPHP3.2.3完全开发手册 - 258 -© 本文档使用 看云 构建 'Article' => array( 'mapping_type' => self::HAS_MANY, 'class_name' => 'Article', 'foreign_key' => 'userId', 'mapping_name' => 'articles', 'mapping_order' => 'create_time desc', // 定义更多的关联属性 …… ), 关联HAS_MANY定义支持的关联属性有: 属性属性 描述描述 class_name 要关联的模型类名 mapping_name 关联的映射名称,用于获取数据用 该名称不要和当前模 型的字段有重复,否则会导致关联数据获取的冲突。 foreign_key 关联的外键名称 parent_key 自引用关联的关联字段 默认为parent_id condition 关联条件 关联查询的时候会自动带上外键的值,如果有 额外的查询条件,可以通过定义关联的condition属 性。 mapping_fields 关联要查询的字段 默认情况下,关联查询的关联数据是 关联表的全部字段,如果只是需要查询个别字段,可以 定义关联的mapping_fields属性。 mapping_limit 关联要返回的记录数目 mapping_order 关联查询的排序 外键的默认规则是当前数据对象名称_id,例如:UserModel对应的可能是表 think_user (注意:think只是一个表前缀,可以随意配置) 那么think_user ThinkPHP3.2.3完全开发手册 - 259 -© 本文档使用 看云 构建 表的外键默认为 user_id,如果不是,就必须在定义关联的时候定义 foreign_key 。 MANY_TO_MANY MANY_TO_MANY 关联表示当前模型可以属于多个对象,而父对象则可能包 含有多个子对象,通常两者之间需要一个中间表类约束和关联。例如每个用户可 以属于多个组,每个组可以有多个用户: 'Group' => self::MANY_TO_MANY 完整定义方式为: 'Group' => array( 'mapping_type' => self::MANY_TO_MANY, 'class_name' => 'Group', 'mapping_name' => 'groups', 'foreign_key' => 'userId', 'relation_foreign_key' => 'groupId', 'relation_table' => 'think_group_user' //此处应显式定义中间表名 称,且不能使用C函数读取表前缀 ) MANY_TO_MANY支持的关联属性定义有: 属性属性 描述描述 class_name 要关联的模型类名 mapping_name 关联的映射名称,用于获取数据用 该名称不要和 当前模型的字段有重复,否则会导致关联数据获 取的冲突。 foreign_key 关联的外键名称 外键的默认规则是当前数据对象 名称_id ThinkPHP3.2.3完全开发手册 - 260 -© 本文档使用 看云 构建 relation_foreign_key 关联表的外键名称 默认的关联表的外键名称是表 名_id mapping_limit 关联要返回的记录数目 mapping_order 关联查询的排序 relation_table 多对多的中间关联表名称 属性属性 描述描述 多对多的中间表默认表规则是: 数据表前缀_关联操作的主表名_关联表名数据表前缀_关联操作的主表名_关联表名 如果think_user 和 think_group 存在一个对应的中间表,默认的表名应该是 如果是由group来操作关联表,中间表应该是 think_group_user,如果是从 user表来操作,那么应该是think_user_group,也就是说,多对多关联的设 置,必须有一个Model类里面需要显式定义中间表,否则双向操作会出错。 中 间表无需另外的id主键(但是这并不影响中间表的操作),通常只是由 user_id 和 group_id 构成。 默认会通过当前模型的getRelationTableName方法来自 动获取,如果当前模型是User,关联模型是Group,那么关联表的名称也就是 使用 user_group这样的格式,如果不是默认规则,需要指定relation_table属 性。 3.2.2版本3.2.2版本开始,relation_table定义支持简化写法,例如: 'relation_table'=>'__USER_GROUP__' 关联查询 由于性能问题,新版取消了自动关联查询机制,而统一使用relation方法进行关 联操作,relation方法不但可以启用关联还可以控制局部关联操作,实现了关联 操作一切尽在掌握之中。 $User = D("User"); $user = $User->relation(true)->find(1); ThinkPHP3.2.3完全开发手册 - 261 -© 本文档使用 看云 构建 输出$user结果可能是类似于下面的数据: array( 'id' => 1, 'account' => 'ThinkPHP', 'password' => '123456', 'Profile' => array( 'email' => 'liu21st@gmail.com', 'nickname' => '流年', ), ) 我们可以看到,用户的关联数据已经被映射到数据对象的属性里面了。其中 Profile就是关联定义的mapping_name属性。 如果我们按照下面的方式定义了as_fields属性的话, protected $_link = array( 'Profile'=>array( 'mapping_type' => self::HAS_ONE, 'class_name' => 'Profile', 'foreign_key' => 'userId', 'as_fields' => 'email,nickname', ), ); 查询的结果就变成了下面的结果 ThinkPHP3.2.3完全开发手册 - 262 -© 本文档使用 看云 构建 array( 'id' => 1, 'account' => 'ThinkPHP', 'password' => 'name', 'email' => 'liu21st@gmail.com', 'nickname' => '流年', ) email和nickname两个字段已经作为user数据对象的字段来显示了。 如果关联数据的字段名和当前数据对象的字段有冲突的话,怎么解决呢? 我们可以用下面的方式来变化下定义: 'as_fields' => 'email,nickname:username', 表示关联表的nickname字段映射成当前数据对象的username字段。 默认会把所有定义的关联数据都查询出来,有时候我们并不希望这样,就可以给 relation方法传入参数来控制要关联查询的。 $User = D("User"); $user = $User->relation('Profile')->find(1); 关联查询一样可以支持select方法,如果要查询多个数据,并同时获取相应的关 联数据,可以改成: $User = D("User"); $list = $User->relation(true)->Select(); 如果希望在完成的查询基础之上 再进行关联数据的查询,可以使用 ThinkPHP3.2.3完全开发手册 - 263 -© 本文档使用 看云 构建 $User = D("User"); $user = $User->find(1); // 表示对当前查询的数据对象进行关联数据获取 $profile = $User->relationGet("Profile"); 事实上,除了当前的参考模型User外,其他的关联模型是不需要创建的。 关联操作 除了关联查询外,系统也支持关联数据的自动写入、更新和删除 关联写入 $User = D("User"); $data = array(); $data["account"] = "ThinkPHP"; $data["password"] = "123456"; $data["Profile"] = array( 'email' =>'liu21st@gmail.com', 'nickname' =>'流年', ); $result = $User->relation(true)->add($data); 这样就会自动写入关联的Profile数据。 同样,可以使用参数来控制要关联写入的数据: $result = $User->relation("Profile")->add($data); 当MANY_TO_MANY时,不建议使用关联插入。 关联更新 数据的关联更新和关联写入类似 ThinkPHP3.2.3完全开发手册 - 264 -© 本文档使用 看云 构建 $User = D("User"); $data["account"] = "ThinkPHP"; $data["password"] = "123456"; $data["Profile"] = array( 'email' =>'liu21st@gmail.com', 'nickname' =>'流年', ); $result = $User-> relation(true)->where(array('id'=>3))->save($dat a); Relation(true)会关联保存User模型定义的所有关联数据,如果只需要关联保 存部分数据,可以使用: $result = $User->relation("Profile")->save($data); 这样就只会同时更新关联的Profile数据。 关联保存的规则: HAS_ONEHAS_ONE : 关联数据的更新直接赋值 HAS_M ANYHAS_M ANY: 的关联数据如果传入主键的值 则表示更新 否则就表示新增 M ANY_T O_M ANYM ANY_T O_M ANY: 的数据更新是删除之前的数据后重新写入 关联删除 //删除用户ID为3的记录的同时删除关联数据 $result = $User->relation(true)->delete("3"); // 如果只需要关联删除部分数据,可以使用 $result = $User->relation("Profile")->delete("3"); ThinkPHP3.2.3完全开发手册 - 265 -© 本文档使用 看云 构建 高级模型 高级模型提供了更多的查询功能和模型增强功能,利用了模型类的扩展机制实 现。如果需要使用高级模型的下面这些功能,记得需要继承 Think\Model\AdvModel类或者采用动态模型。 namespace Home\Model; use Think\Model\AdvModel; class UserModel extends AdvModel{ } 我们下面的示例都假设UserModel类继承自Think\Model\AdvModel类。 字段过滤 基础模型类内置有数据自动完成功能,可以对字段进行过滤,但是必须通过 Create方法调用才能生效。高级模型类的字段过滤功能却可以不受create方法 的调用限制,可以在模型里面定义各个字段的过滤机制,包括写入过滤和读取过 滤。 字段过滤的设置方式只需要在Model类里面添加 $_filter 属性,并且加入过滤 因子,格式如下: protected $_filter = array( '过滤的字段'=>array('写入过滤规则','读取过滤规则',是否传入整个数据 对象), ) 过滤的规则是一个函数,如果设置传入整个数据对象,那么函数的参数就是整个 数据对象,默认是传入数据对象中该字段的值。 ThinkPHP3.2.3完全开发手册 - 266 -© 本文档使用 看云 构建 举例说明,例如我们需要在发表文章的时候对文章内容进行安全过滤,并且希望 在读取的时候进行截取前面255个字符,那么可以设置: protected $_filter = array( 'content'=>array('contentWriteFilter','contentReadFilter'), ) 其中,contentWriteFilter是自定义的对字符串进行安全过滤的函数,而 contentReadFilter是自定义的一个对内容进行截取的函数。通常我们可以在项 目的公共函数文件里面定义这些函数。 序列化字段 序列化字段是新版推出的新功能,可以用简单的数据表字段完成复杂的表单数据 存储,尤其是动态的表单数据字段。 要使用序列化字段的功能,只需要在模型 中定义serializeField属性,定义格式如下: protected $serializeField = array( 'info' => array('name', 'email', 'address'), ); Info是数据表中的实际存在的字段,保存到其中的值是name、email和 address三个表单字段的序列化结果。序列化字段功能可以在数据写入的时候进 行自动序列化,并且在读出数据表的时候自动反序列化,这一切都无需手动进 行。 下面还是是User数据表为例,假设其中并不存在name、email和address字 段,但是设计了一个文本类型的info字段,那么下面的代码是可行的: ThinkPHP3.2.3完全开发手册 - 267 -© 本文档使用 看云 构建 $User = D("User"); // 实例化User对象 // 然后直接给数据对象赋值 $User->name = 'ThinkPHP'; $User->email = 'ThinkPHP@gmail.com'; $User->address = '上海徐汇区'; // 把数据对象添加到数据库 name email和address会自动序列化后保存到 info字段 $User->add(); 查询用户数据信息 $User->find(8); // 查询结果会自动把info字段的值反序列化后生成name、email和addres s属性 // 输出序列化字段 echo $User->name; echo $User->email; echo $User->address; 文本字段 ThinkPHP支持数据模型中的个别字段采用文本方式存储,这些字段就称为文本 字段,通常可以用于某些Text或者Blob字段,或者是经常更新的数据表字段。 要使用文本字段非常简单,只要在模型里面定义blobFields属性就行了。例如, 我们需要对Blog模型的content字段使用文本字段,那么就可以使用下面的定 义: Protected $blobFields = array('content'); 系统在查询和写入数据库的时候会自动检测文本字段,并且支持多个字段的定 义。 需要注意的是:对于定义的文本字段并不需要数据库有对应的字段,完全是 另外的。而且,暂时不支持对文本字段的搜索功能。 ThinkPHP3.2.3完全开发手册 - 268 -© 本文档使用 看云 构建 只读字段 只读字段用来保护某些特殊的字段值不被更改,这个字段的值一旦写入,就无法 更改。 要使用只读字段的功能,我们只需要在模型中定义readonlyField属性 protected $readonlyField = array('name', 'email'); 例如,上面定义了当前模型的name和email字段为只读字段,不允许被更改。 也就是说当执行save方法之前会自动过滤到只读字段的值,避免更新到数据 库。 下面举个例子说明下: $User = D("User"); // 实例化User对象 $User->find(8); // 更改某些字段的值 $User->name = 'TOPThink'; $User->email = 'Topthink@gmail.com'; $User->address = '上海静安区'; // 保存更改后的用户数据 $User->save(); 事实上,由于我们对name和email字段设置了只读,因此只有address字段的 值被更新了,而name和email的值仍然还是更新之前的值。 悲观锁和乐观锁 业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在金融系统的日终 结算处理中,我们希望针对某个时间点的数据进行处理,而不希望在结算进行过 程中(可能是几秒种,也可能是几个小时),数据再发生变化。此时,我们就需 要通过一些机制来保证这些数据在某个操作过程中不会被外界修改,这样的机 制,在这里,也就是所谓的 “ 锁 ” ,即给我们选定的目标数据上锁,使其无 ThinkPHP3.2.3完全开发手册 - 269 -© 本文档使用 看云 构建 法被其他程序修改。 ThinkPHP支持两种锁机制:即通常所说的 “ 悲观锁( Pessimistic Locking ) ”和 “ 乐观锁( Optimistic Locking ) ” 。 悲观锁( Pessimistic Locking ) 悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及 来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将 数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据 库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现 了加锁机制,也无法保证外部系统不会修改数据)。 通常是使用for update子 句来实现悲观锁机制。 ThinkPHP支持悲观锁机制,默认情况下,是关闭悲观锁功能的,要在查询和更 新的时候启用悲观锁功能,可以通过使用之前提到的查询锁定方法,例如: $User->lock(true)->save($data);// 使用悲观锁功能 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下 依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数 据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。 如一 个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行 修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过 程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操 作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面 对几百上千个并发,这样的情况将导致怎样的后果。乐观锁机制在一定程度上解 决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何 谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中, 一般是通过为数据库表增加一个 “version” 字段来实现。 ThinkPHP也可以支持乐观锁机制,要启用乐观锁,只需要继承高级模型类并定 义模型的optimLock属性,并且在数据表字段里面增加相应的字段就可以自动 ThinkPHP3.2.3完全开发手册 - 270 -© 本文档使用 看云 构建 启用乐观锁机制了。默认的optimLock属性是lock_version,也就是说如果要 在User表里面启用乐观锁机制,只需要在User表里面增加lock_version字段, 如果有已经存在的其它字段作为乐观锁用途,可以修改模型类的optimLock属 性即可。如果存在optimLock属性对应的字段,但是需要临时关闭乐观锁机 制,把optimLock属性设置为false就可以了。 数据分表 对于大数据量的应用,经常会对数据进行分表,有些情况是可以利用数据库的分 区功能,但并不是所有的数据库或者版本都支持,因此我们可以利用ThinkPHP 内置的数据分表功能来实现。帮助我们更方便的进行数据的分表和读取操作。 和数据库分区功能不同,内置的数据分表功能需要根据分表规则手动创建相应的 数据表。 在需要分表的模型中定义partition属性即可。 protected $partition = array( 'field' => 'name',// 要分表的字段 通常数据会根据某个字段的值按照规则 进行分表 'type' => 'md5',// 分表的规则 包括id year mod md5 函数 和首字母 'expr' => 'name',// 分表辅助表达式 可选 配合不同的分表规则 'num' => 'name',// 分表的数目 可选 实际分表的数量 ); 定义好了分表属性后,我们就可以来进行CURD操作了,唯一不同的是,获取当 前的数据表不再使用getTableName方法,而是使用 getPartitionTableName方法,而且必须传入当前的数据。然后根据数据分析 应该实际操作哪个数据表。因此,分表的字段值必须存在于传入的数据中,否则 会进行联合查询。 返回类型 系统默认的数据库查询返回的是数组,我们可以给单个数据设置返回类型,以满 ThinkPHP3.2.3完全开发手册 - 271 -© 本文档使用 看云 构建 足特殊情况的需要,例如: $User = M("User"); // 实例化User对象 // 返回结果是一个数组数据 $data = $User->find(6); // 返回结果是一个stdClass对象 $data = $User->returnResult($data, "object"); // 还可以返回自定义的类 $data = $User->returnResult($data, "User"); 返回自定义的User类,类的架构方法的参数是传入的数据。例如: Class User { public function __construct($data){ // 对$data数据进行处理 } } Mongo模型 Mongo模型是专门为Mongo数据库驱动而支持的Model扩展,如果需要操作 Mongo数据库的话,自定义的模型类必须继承 Think\Model\MongoModel。 Mongo模型为操作Mongo数据库提供了更方便的实用功能和查询用法,包 括: 1. 对MongoId对象和非对象主键的全面支持; 2. 保持了动态追加字段的特性; 3. 数字自增字段的支持; ThinkPHP3.2.3完全开发手册 - 272 -© 本文档使用 看云 构建 4. 执行SQL日志的支持; 5. 字段自动检测的支持; 6. 查询语言的支持; 7. MongoCode执行的支持; 主键 系统很好的支持Mongo的主键类型,Mongo默认的主键名是 _id,也可以通过 设置pk属性改变主键名称(也许你需要用其他字段作为数据表的主键),例 如: namespace Home\Model; use Think\Model\MongoModel; Class UserModel extends MongoModel { Protected $pk = 'id'; } 主键支持三种类型(通过_idType属性设置),分别是: 类型类型 描述描述 self::TYPE_OBJECT 或者1 (默认类型) 采用MongoId对象,写入或者查询 的时候传入数字或者字符会自动转换,获取的时候 会自动转换成字符串。 self::TYPE_INT或者 2 整形,支持自动增长,通过设置_autoInc 属性 self::TYPE_STRING 或者3 字符串hash 设置主键类型示例: ThinkPHP3.2.3完全开发手册 - 273 -© 本文档使用 看云 构建 namespace Home\Model; use Think\Model\MongoModel; Class UserModel extends MongoModel { Protected $_idType = self::TYPE_INT; protected $_autoinc = true; } 字段检测 MongoModel默认关闭字段检测,是为了保持Mongo的动态追加字段的特 性,如果你的应用不需要使用Mongo动态追加字段的特性,可以设置 autoCheckFields 为true即可开启字段检测功能,提高安全性。一旦开启字段 检测功能后,系统会自动查找当前数据表的第一条记录来获取字段列表。 如果你关闭字段检测功能的话,将不能使用查询的字段排除功能。 连贯操作 MongoModel中有部分连贯操作暂时不支持,包括:group、union、join、 having、lock和distinct操作。其他连贯操作都可以很好的支持,例如: $Model = new Think\Model\MongoModel("User"); $Model->field("name,email,age")->order("status desc")->limit("10, 8")->select(); 查询支持 Mongo数据库的查询条件和其他数据库有所区别。 1. 首先,支持所有的普通查询和快捷查询; 2. 表达式查询增加了一些针对MongoDb的查询用法; 3. 统计查询目前只能支持count操作,其他的可能要自己通过MongoCode来 实现了; ThinkPHP3.2.3完全开发手册 - 274 -© 本文档使用 看云 构建 MongoModel的组合查询支持 _string 采用MongoCode查询 _query 和其他数据库的请求字符串查询相同 _complex MongoDb暂不支持 MongoModel提供了MongoCode方法,可以支持MongoCode方式的查询 或者操作。 表达式查询 表达式查询采用下面的方式: $map['字段名'] = array('表达式','查询条件'); 因为MongoDb的特性,MongoModel的表达式查询和其他的数据库有所区 别,增加了一些新的用法。 表达式不分大小写,支持的查询表达式和Mongo原生的查询语法对照如下: 查询表达式查询表达式 含义含义 M ongo原生查询M ongo原生查询 条件条件 neq 或者ne 不等于 $ne lt 小于 $lt lte 或者elt 小于等于 $lte gt 大于 $gt gte 或者egt 大于等于 $gte like 模糊查询 用MongoRegex正 则模拟 无 mod 取模运算 $mod ThinkPHP3.2.3完全开发手册 - 275 -© 本文档使用 看云 构建 in in查询 $in nin或者not in not in查询 $nin all 满足所有条件 $all between 在某个的区间 无 not between 不在某个区间 无 exists 字段是否存在 $exists size 限制属性大小 $size type 限制字段类型 $type regex MongoRegex正则查询 MongoRegex实现 exp 使用MongoCode查询 无 查询表达式查询表达式 含义含义 M ongo原生查询M ongo原生查询 条件条件 注意,在使用like查询表达式的时候,和mysql的方式略有区别,对应关系如 下: M ysql模糊查询M ysql模糊查询 M ongo模糊查询M ongo模糊查询 array('like','%thinkphp%'); array('like','thinkphp'); array('like','thinkphp%'); array('like','^thinkphp'); array('like','%thinkphp'); array('like','thinkphp$'); LIKE: 同sql的LIKE 例如: $map['name'] = array('like','^thinkphp'); 查询条件就变成 name like 'thinkphp%' 设置支持 Mongo的数据更新设置用于数据保存和写入操作,可以支持: ThinkPHP3.2.3完全开发手册 - 276 -© 本文档使用 看云 构建 表达式表达式 含义含义 M ongo原M ongo原 生用法生用法 inc 数字字段增长或减少 $inc set 字段赋值 $set unset 删除字段值 $unset push 追加一个值到字段(必须是数组类型)里面去 $push pushall 追加多个值到字段(必须是数组类型)里面去 $pushall addtoset 增加一个值到字段(必须是数组类型)内,而 且只有当这个值不在数组内才增加 $addtoset pop 根据索引删除字段(必须是数组字段)中的一 个值 $pop pull 根据值删除字段(必须是数组字段)中的一个 值 $pull pullall 一次删除字段(必须是数组字段)中的多个值 $pullall 例如, $data['id'] = 5; $data['score'] = array('inc',2); $Model->save($data); 其他 MongoModel增加了几个方法 mongoCodemongoCode 执行MongoCode getM ongoNextIdgetM ongoNextId ([字段名]) 获取自增字段的下一个ID,可用于数字主键或 者其他需要自增的字段,参数为空的时候表示或者主键的。 ThinkPHP3.2.3完全开发手册 - 277 -© 本文档使用 看云 构建 ClearClear 清空当前数据表方法 视图 模板定义 每个模块的模板文件是独立的,为了对模板文件更加有效的管理,ThinkPHP对 模板文件进行目录划分,默认的模板文件定义规则是: 视图目录/[模板主题/]控制器名/操作名+模板后缀 默认的视图目录是模块的View目录模块的View目录(模块可以有多个视图文件目录,这取决于 你的应用需要),框架的默认视图文件后缀是 .html 。 新版模板主题默认是空 (表示不启用模板主题功能)。 在每个模板主题下面,是以模块下面的控制器名为目录,然后是每个控制器的具 体操作模板文件,例如: User控制器的add操作 对应的模板文件就应该是: ./Application/Home/View/User/add.html 如果你的默认视图层不是View,例如: 'DEFAULT_V_LAYER' => 'Template', // 设置默认的视图层名称 那么,对应的模板文件就变成了: ./Application/Home/Template/User/add.html 。 ThinkPHP3.2.3完全开发手册 - 278 -© 本文档使用 看云 构建 模板文件的默认后缀的情况是 .html ,也可以通过 T M PL_T EM PLAT E_SUFFIXT M PL_T EM PLAT E_SUFFIX 来配置成其他的。例如,我们可以配置: 'TMPL_TEMPLATE_SUFFIX'=>'.tpl' 定义后,User控制器的add操作 对应的模板文件就变成是: ./Application/Home/View/User/add.tpl 如果觉得目录结构太深,可以通过设置 T M PL_FILE_DEPRT M PL_FILE_DEPR 参数来配置简化 模板的目录层次,例如设置: 'TMPL_FILE_DEPR'=>'_' 默认的模板文件就变成了: ./Application/Home/View/User_add.html 支持把模板目录设置到模块目录之外,有两种方式: 一、改变所有模块的模板文件目录一、改变所有模块的模板文件目录 可以通过设置TMPL_PATH常量来改变所有模块的模板目录所在,例如: define('TMPL_PATH','./Template/'); 原来的 ./Application/Home/View/User/add.html 变成了 ./Template/Home/User/add.html 。 二、改变某个模块的模板文件目录二、改变某个模块的模板文件目录 我们可以在模块配置文件中设置 VIEW_PATH参数单独定义某个模块的视图目录,例如: 'VIEW_PATH'=>'./Theme/' 把当前模块的视图目录指定到最外层的Theme目录下面,而不是放到当前模块 ThinkPHP3.2.3完全开发手册 - 279 -© 本文档使用 看云 构建 的View目录下面。 原来的 ./Application/Home/View/User/add.html 变成了 ./Theme/User/add.html 。 如果同时定义了TMPL_PATH常量和VIEW_PATH设置参数,那么以当前模 块的VIEW_PATH参数设置优先。 模板主题 一个模块如果需要支持多套模板文件的话,就可以使用模板主题功能。 默认情 况下,没有开启模板主题功能,如果需要开启,设置 DEFAULT _T HEM EDEFAULT _T HEM E 参 数即可: // 设置默认的模板主题 'DEFAULT_THEME' => 'default' 采用模板主题后,需要在视图目录下面创建对应的主题目录,和不启用模板主题 的情况相比,模板文件只是多了一层目录: View/User/add.html // 没有启用模板主题之前 View/default/User/add.html // 启用模板主题之后 在视图渲染输出之前,我们可以通过动态设置来改变需要使用的模板主题。 // 在控制器中动态改变模板主题 $this->theme('blue')->display('add'); 模板赋值 ThinkPHP3.2.3完全开发手册 - 280 -© 本文档使用 看云 构建 如果要在模板中输出变量,必须在在控制器中把变量传递给模板,系统提供了 assign方法对模板变量赋值,无论何种变量类型都统一使用assign赋值。 $this->assign('name',$value); // 下面的写法是等效的 $this->name = $value; assign方法必须在 display和show方法 之前调用,并且系统只会输出设定的变 量,其它变量不会输出(系统变量例外),一定程度上保证了变量的安全性。 系统变量可以通过特殊的标签输出,无需赋值模板变量 赋值后,就可以在模板文件中输出变量了,如果使用的是内置模板的话,就可以 这样输出: {$name} 如果要同时输出多个模板变量,可以使用下面的方式: $array['name'] = 'thinkphp'; $array['email'] = 'liu21st@gmail.com'; $array['phone'] = '12335678'; $this->assign($array); 这样,就可以在模板文件中同时输出name、email和phone三个变量。 模板变量的输出根据不同的模板引擎有不同的方法,我们在后面会专门讲解内置 模板引擎的用法。如果你使用的是PHP本身作为模板引擎的话 ,就可以直接在 模板文件里面输出了: 如果采用内置的模板引擎,可以使用: {$name} [ {$email} {$phone} ] 输出 同样的内容。 关于更多的模板标签使用,我们会在后面模板标签中详细讲解。 ThinkPHP3.2.3完全开发手册 - 281 -© 本文档使用 看云 构建 模板渲染 模板定义后就可以渲染模板输出,系统也支持直接渲染内容输出,模板赋值必须 在模板渲染之前操作。 渲染模板 渲染模板输出最常用的是使用display方法,调用格式: display('[模板文件]'[,'字符编码'][,'输出类型'])模板文件的写法支持下面几种: 用法用法 描述描述 不带任何参数 自动定位当前操作的模板文件 [模块@][控制器:][操 作] 常用写法,支持跨模块 模板主题可以和theme方 法配合 完整的模板文件名 直接使用完整的模板文件名(包括模板后缀) 下面是一个最典型的用法,不带任何参数: // 不带任何参数 自动定位当前操作的模板文件 $this->display(); 表示系统会按照默认规则自动定位模板文件,其规则是: 如果当前没有启用模板主题则定位到: 当前模块/默认视图目录/当前控制器/当前操作.html 如果有启用模板主题则定 位到: 当前模块/默认视图目录/当前主题/当前控制器/当前操作.html 如果有更改TMPL_FILE_DEPR设置(假设 'TMPL_FILE_DEPR'=>'_' )的 话,则上面的自动定位规则变成: ThinkPHP3.2.3完全开发手册 - 282 -© 本文档使用 看云 构建 当前模块/默认视图目录/当前控制器_当前操作.html 和 当前模块/默认视图目录/当前主题/当前控制器_当前操作.html 。 所以通常display方法无需带任何参数即可输出对应的模板,这是模板输出的最 简单的用法。 通常默认的视图目录是View 如果没有按照模板定义规则来定义模板文件(或者需要调用其他控制器下面的某 个模板),可以使用: // 指定模板输出 $this->display('edit'); 表示调用当前控制器下面的edit模板 $this->display('Member:read'); 表示调用Member控制器下面的read模板。 如果我们使用了模板主题功能,那么也可以支持跨主题调用,使用: $this->theme('blue')->display('User:edit'); 表示调用blue主题下面的User控制器的edit模板。 如果你不希望每个主题都重复定义一些相同的模版文件的话,还可以启用差异主 题定义方式,设置: 'TMPL_LOAD_DEFAULTTHEME'=>true 设置后,如果blue主题下面不存在edit模板的话,就会自动定位到默认主题中 ThinkPHP3.2.3完全开发手册 - 283 -© 本文档使用 看云 构建 的edit模板。 渲染输出不需要写模板文件的路径和后缀,确切地说,这里面的控制器和操作并 不一定需要有实际对应的控制器和操作,只是一个目录名称和文件名称而已,例 如,你的项目里面可能根本没有Public控制器,更没有Public控制器的menu操 作,但是一样可以使用 $this->display('Public:menu'); 输出这个模板文件。理解了这个,模板输出就清晰了。 display方法支持在渲染输出的时候指定输出编码和类型,例如,可以指定编码 和类型: $this->display('read', 'utf-8', 'text/xml'); 表示输出XML页面类型(配合你的应用需求可以输出很多类型)。 事情总有特例,如果的模板目录是自定义的,或者根本不需要按模块进行分目录 存放,那么默认的display渲染规则就不能处理,这个时候,我们就需要使用另 外一种方式来应对,直接传入模板文件名即可,例如: $this->display('./Template/Public/menu.html'); 这种方式需要指定模板路径和后缀,这里的Template/Public目录是位于当前 项目入口文件位置下面。如果是其他的后缀文件,也支持直接输出,例如: $this->display('./Template/Public/menu.tpl'); 只要 ./Template/Public/menu.tpl 是一个实际存在的模板文件。 要注意模板文件位置是相对于项目的入口文件,而不是模板目录。 ThinkPHP3.2.3完全开发手册 - 284 -© 本文档使用 看云 构建 获取模板地址 为了更方便的输出模板文件,新版封装了一个T函数用于生成模板文件名。 用法: T([资源://][模块@][主题/][控制器/]操作,[视图分层]) T函数的返回值是一个完整的模板文件名,可以直接用于display和fetch方法进 行渲染输出。 例如: T('Public/menu'); // 返回 当前模块/View/Public/menu.html T('blue/Public/menu'); // 返回 当前模块/View/blue/Public/menu.html T('Public/menu','Tpl'); // 返回 当前模块/Tpl/Public/menu.html T('Public/menu'); // 如果TMPL_FILE_DEPR 为 _ 返回 当前模块/Tpl/Public_menu.html T('Public/menu'); // 如果TMPL_TEMPLATE_SUFFIX 为.tpl 返回 当前模块/Tpl/Public/me nu.tpl T('Admin@Public/menu'); // 返回 Admin/View/Public/menu.html T('Extend://Admin@Public/menu'); // 返回 Extend/Admin/View/Public/menu.html (Extend目录取决于AU TOLOAD_NAMESPACE中的配置) 在display方法中直接使用T函数: ThinkPHP3.2.3完全开发手册 - 285 -© 本文档使用 看云 构建 // 使用T函数输出模板 $this->display(T('Admin@Public/menu')); T函数可以输出不同的视图分层模板。 获取内容 如果需要获取渲染模板的输出内容而不是直接输出,可以使用fetch方法。 fetch方法的用法和display基本一致(只是不需要指定输出编码和输出类型): fetch('模板文件')模板文件的调用方法和display方法完全一样,区别就在于 fetch方法渲染后不是直接输出,而是返回渲染后的内容,例如: $content = $this->fetch('Member:edit'); 使用fetch方法获取渲染内容后,你可以进行过滤和替换等操作,或者用于对输 出的复杂需求。 渲染内容 如果你没有定义任何模板文件,或者把模板内容存储到数据库中的话,你就需要 使用show方法来渲染输出了,show方法的调用格式: show('渲染内容'[,'字符编码'][,'输出类型'])例如, $this->show($content); 也可以指定编码和类型: $this->show($content, 'utf-8', 'text/xml'); show方法中的内容也可以支持模板解析。 ThinkPHP3.2.3完全开发手册 - 286 -© 本文档使用 看云 构建 模板引擎 系统支持原生的PHP模板,而且本身内置了一个基于XML的高效的编译型模板 引擎,系统默认使用的模板引擎是内置模板引擎,关于这个模板引擎的标签详细 使用可以参考模版引擎部分。 内置的模板引擎也可以直接支持在模板文件中采用PHP原生代码和模板标签的 混合使用,如果需要完全使用PHP本身作为模板引擎,可以配置: 'TMPL_ENGINE_TYPE' =>'PHP' 可以达到最佳的效率。 如果你使用了其他的模板引擎,只需要设置TMPL_ENGINE_TYPE参数为相关 的模板引擎名称即可。 模板 本章的内容主要讲述了如何使用内置的模板引擎来定义模板文件,以及使用加载 文件、模板布局和模板继承等高级功能。 ThinkPHP内置了一个基于XML的性能卓越的模板引擎 ThinkTemplate,这是 一个专门为ThinkPHP服务的内置模板引擎。ThinkTemplate是一个使用了 XML标签库技术的编译型模板引擎,支持两种类型的模板标签,使用了动态编 译和缓存技术,而且支持自定义标签库。其特点包括: 支持XML标签库和普通标签的混合定义; 支持直接使用PHP代码书写; 支持文件包含; 支持多级标签嵌套; 支持布局模板功能; 一次编译多次运行,编译和运行效率非常高; ThinkPHP3.2.3完全开发手册 - 287 -© 本文档使用 看云 构建 模板文件和布局模板更新,自动更新模板缓存; 系统变量无需赋值直接输出; 支持多维数组的快速输出; 支持模板变量的默认值; 支持页面代码去除Html空白; 支持变量组合调节器和格式化功能; 允许定义模板禁用函数和禁用PHP语法; 通过标签库方式扩展。 每个模板文件在执行过程中都会生成一个编译后的缓存文件,其实就是一个可以 运行的PHP文件。模板缓存默认位于项目的Runtime/模块/Cache目录下面, 以模板文件的md5编码作为缓存文件名保存的。如果在模板标签的使用过程中 发现问题,可以尝试通过查看模板缓存文件找到问题所在。 内置的模板引擎支持普通标签和XML标签方式两种标签定义,分别用于不同的 目的: 标签类型标签类型 描述描述 普通标签 主要用于输出变量和做一些基本的操作 XML标签 主要完成一些逻辑判断、控制和循环输出,并且可扩展 这种方式的结合保证了模板引擎的简洁和强大的有效融合。 变量输出 在模板中输出变量的方法很简单,例如,在控制器中我们给模板变量赋值: ThinkPHP3.2.3完全开发手册 - 288 -© 本文档使用 看云 构建 $name = 'ThinkPHP'; $this->assign('name',$name); $this->display(); 然后就可以在模板中使用: Hello,{$name}! 模板编译后的结果就是: Hello,! 这样,运行的时候就会在模板中显示: Hello,ThinkPHP! 注意模板标签的 { 和 $ 之间不能有任何的空格,否则标签无效。所以,下面的标 签 Hello,{ $name}! 将不会正常输出name变量,而是直接保持不变输出: Hello,{ $name}! 普通标签默认开始标记是 { ,结束标记是 } 。也可以通过设置 TMPL_L_DELIM 和 TMPL_R_DELIM 进行更改。例如,我们在项目配置文件 中定义: 'TMPL_L_DELIM'=>'<{', 'TMPL_R_DELIM'=>'}>', 那么,上面的变量输出标签就应该改成: ThinkPHP3.2.3完全开发手册 - 289 -© 本文档使用 看云 构建 Hello,<{$name}>! 后面的内容我们都以默认的标签定义来说明。 模板标签的变量输出根据变量类型有所区别,刚才我们输出的是字符串变量,如 果是数组变量, $data['name'] = 'ThinkPHP'; $data['email'] = 'thinkphp@qq.com'; $this->assign('data',$data); 那么,在模板中我们可以用下面的方式输出: Name:{$data.name} Email:{$data.email} 或者用下面的方式也是有效: Name:{$data['name']} Email:{$data['email']} 当我们要输出多维数组的时候,往往要采用后面一种方式。 如果data变量是一个对象(并且包含有name和email两个属性),那么可以用 下面的方式输出: Name:{$data:name} Email:{$data:email} 或者 ThinkPHP3.2.3完全开发手册 - 290 -© 本文档使用 看云 构建 Name:{$data->name} Email:{$data->email} 系统变量 系统变量输出 普通的模板变量需要首先赋值后才能在模板中输出,但是系统变量则不需要,可 以直接在模板中输出,系统变量的输出通常以{$T hink{$T hink 打头,例如: {$Think.server.script_name} // 输出$_SERVER['SCRIPT_NAME']变量 {$Think.session.user_id} // 输出$_SESSION['user_id']变量 {$Think.get.pageNumber} // 输出$_GET['pageNumber']变量 {$Think.cookie.name} // 输出$_COOKIE['name']变量 支持输出 $_SERVER 、 $_ENV 、 $_POST 、 $_GET 、 $_REQUEST 、 $_SESSION 和 $_COOKIE 变量。 常量输出 还可以输出常量 {$Think.const.MODULE_NAME} 或者直接使用 {$Think.MODULE_NAME} 配置输出 ThinkPHP3.2.3完全开发手册 - 291 -© 本文档使用 看云 构建 输出配置参数使用: {$Think.config.db_charset} {$Think.config.url_model} 语言变量 输出语言变量可以使用: {$Think.lang.page_error} {$Think.lang.var_error} 使用函数 我们往往需要对模板输出变量使用函数,可以使用: {$data.name|md5} 编译后的结果是: 如果函数有多个参数需要调用,则使用: {$create_time|date="y-m-d",###} 表示date函数传入两个参数,每个参数用逗号分割,这里第一个参数是 y-m-d ,第二个参数是前面要输出的 create_time 变量,因为该变量是第二个参数, 因此需要用###标识变量位置,编译后的结果是: ThinkPHP3.2.3完全开发手册 - 292 -© 本文档使用 看云 构建 如果前面输出的变量在后面定义的函数的第一个参数,则可以直接使用: {$data.name|substr=0,3} 表示输出 虽然也可以使用: {$data.name|substr=###,0,3} 但完全没用这个必要。 还可以支持多个函数过滤,多个函数之间用“|”分割即可,例如: {$name|md5|strtoupper|substr=0,3} 编译后的结果是: 函数会按照从左到右的顺序依次调用。 如果你觉得这样写起来比较麻烦,也可以直接这样写: {:substr(strtoupper(md5($name)),0,3)} ThinkPHP3.2.3完全开发手册 - 293 -© 本文档使用 看云 构建 变量输出使用的函数可以支持内置的PHP函数或者用户自定义函数,甚至是 静态方法。 默认值输出 我们可以给变量输出提供默认值,例如: {$user.nickname|default="这家伙很懒,什么也没留下"} 对系统变量依然可以支持默认值输出,例如: {$Think.get.name|default="名称为空"} 默认值和函数可以同时使用,例如: {$Think.get.name|getName|default="名称为空"} 使用运算符 我们可以对模板输出使用运算符,包括对“+”“ ” “*” “/”和“%”的支 持。 例如: 运算符运算符 使用示例使用示例 + {$a+$b} - {$a-$b} ThinkPHP3.2.3完全开发手册 - 294 -© 本文档使用 看云 构建 * {$a*$b} / {$a/$b} % {$a%$b} ++ {$a++} 或 {++$a} -- {$a--} 或 {--$a} 综合运算 {$a+$b*10+$c} 运算符运算符 使用示例使用示例 在使用运算符的时候,不再支持点语法和常规的函数用法,例如: {$user.score+10} //错误的 {$user['score']+10} //正确的 {$user['score']*$user['level']} //正确的 {$user['score']|myFun*10} //错误的 {$user['score']+myFun($user['level'])} //正确的 标签库 内置的模板引擎除了支持普通变量的输出之外,更强大的地方在于标签库功能。 标签库类似于Java的Struts中的JSP标签库,每一个标签库是一个独立的标签库 文件,标签库中的每一个标签完成某个功能,采用XML标签方式(包括开放标 签和闭合标签)。 标签库分为内置和扩展标签库,内置标签库是Cx标签库。 导入标签库 使用taglib标签导入当前模板中需要使用的标签库,例如: ThinkPHP3.2.3完全开发手册 - 295 -© 本文档使用 看云 构建 如果没有定义html标签库的话,则导入无效。 也可以导入多个标签库,使用: 导入标签库后,就可以使用标签库中定义的标签了,假设article标签库中定义了 read标签: {$data.id}:{$data.title} 在上面的标签中, ... 就是闭合标签,起始和 结束标签必须成对出现。 如果是 就是开放标签。 闭合和开放标签取决于标签库中的定义,一旦定义后就不能混淆使用,否则 就会出现错误。 内置标签 内置标签库无需导入即可使用,并且不需要加XML中的标签库前 缀,ThinkPHP内置的标签库是Cx标签库,所以,Cx标签库中的所有标签,我 们可以在模板文件中直接使用,我们可以这样使用: ThinkPHP3.2.3完全开发手册 - 296 -© 本文档使用 看云 构建 {$data.id}:{$data.title} 标签库预加载 标签库预加载是指无需手动在模板文件中导入标签库即可使用标签库中的标签, 通常用于某个标签库需要被大多数模板使用的情况。 在应用或者模块的配置文件中添加: 'TAGLIB_PRE_LOAD' => 'article,html' ThinkPHP3.2.3完全开发手册 - 297 -© 本文档使用 看云 构建 设置后,模板文件就不再需要使用 但是仍然可以在模板中调用: {$data.id}:{$data.title} 模板继承 模板继承是一项更加灵活的模板布局方式,模板继承不同于模板布局,甚至来 说,应该在模板布局的上层。模板继承其实并不难理解,就好比类的继承一样, 模板也可以定义一个基础模板(或者是布局),并且其中定义相关的区块 (block),然后继承(extend)该基础模板的子模板中就可以对基础模板中 定义的区块进行重载。 因此,模板继承的优势其实是设计基础模板中的区块(block)和子模板中替换 这些区块。 每个区块由 标签组成。 下面就是基础模板中的一个典型的 区块设计(用于设计网站标题): 网站标题 block标签必须指定name属性来标识当前区块的名称,这个标识在当前模板中 应该是唯一的,block标签中可以包含任何模板内容,包括其他标签和变量,例 如: ThinkPHP3.2.3完全开发手册 - 298 -© 本文档使用 看云 构建 {$web_title} 你甚至还可以在区块中加载外部文件: 一个模板中可以定义任意多个名称标识不重复的区块,例如下面定义了一个 base.html 基础模板: 标题 菜单 左边分栏 主内容 右边分栏 底部 然后我们在子模板(其实是当前操作的入口模板)中使用继承: ThinkPHP3.2.3完全开发手册 - 299 -© 本文档使用 看云 构建 {$title} 首页 资讯 论坛 {$vo.title}
{$vo.content}
最新资讯: {$new.title}
@ThinkPHP2012 版权所有 可以看到,子模板中使用了extend标签定义需要继承的模板,extend标签的用 法和include标签一样,你也可以加载其他模板: 或者使用绝对文件路径加载 ThinkPHP3.2.3完全开发手册 - 300 -© 本文档使用 看云 构建 在当前子模板中,只能定义区块而不能定义其他的模板内容,否则将会直接忽 略,并且只能定义基础模板中已经定义的区块。 例如,如果采用下面的定义: {$title} 首页 资讯 论坛 导航部分将是无效的,不会显示在模板中。 在子模板中,可以对基础模板中的区块进行重载定义,如果没有重新定义的话, 则表示沿用基础模板中的区块定义,如果定义了一个空的区块,则表示删除基础 模板中的该区块内容。 上面的例子,我们就把left区块的内容删除了,其他的区 块都进行了重载。 子模板中的区块定义顺序是随意的,模板继承的用法关键在于基础模板如何布局 和设计规划了,如果结合原来的布局功能,则会更加灵活。 修改定界符 模板文件可以包含普通模板标签和XML模板标签,标签的定界符都可以重新配 置。 普通标签 内置模板引擎的普通模板标签默认以{ 和 } 作为开始和结束标识,并且在开始标 记紧跟标签的定义,如果之间有空格或者换行则被视为非模板标签直接输出。 例如: {$name} 、 {$vo.name} 、 {$vo['name']|strtoupper} 都属于普通模 板标签。 ThinkPHP3.2.3完全开发手册 - 301 -© 本文档使用 看云 构建 要更改普遍模板的起始标签和结束标签,请使用下面的配置参数: TMPL_L_DELIM //模板引擎普通标签开始标记 TMPL_R_DELIM //模板引擎普通标签结束标记 例如在项目配置文件中增加下面的配置: 'TMPL_L_DELIM' => '<{', 'TMPL_R_DELIM' => '}>' 普通标签的定界符就被修改了,原来的 {$name} 和 {$vo.name} 必须使用 <{$name}> 和 <{$vo.name}> 才能生效了。 如果你定制了普通标签的定界符,记得修改下默认的系统模板。 XML标签 普通模板标签主要用于模板变量输出和模板注释。如果要使用其它功能,请使用 XML模板标签。XML模板标签可以用于模板变量输出、文件包含、条件控制、 循环输出等功能,而且完全可以自己扩展功能。如果你觉得XML标签无法在正 在使用的编辑器里面无法编辑,还可以更改XML标签库的起始和结束标签,请 修改下面的配置参数: TAGLIB_BEGIN //标签库标签开始标签 TAGLIB_END //标签库标签结束标记 例如在项目配置文件中增加下面的配置: 'TAGLIB_BEGIN'=>'[', 'TAGLIB_END'=>']', ThinkPHP3.2.3完全开发手册 - 302 -© 本文档使用 看云 构建 原来的 相等 不相等 就必须改成 [eq name="name" value="value"] 相等 [else/] 不相等 [/eq] 注意:XML标签和普通标签的定界符不能冲突,否则会导致解析错误。 三元运算 模板可以支持三元运算符,例如: {$status?'正常':'错误'} {$info['status']?$info['msg']:$info['error']} 注意:三元运算符中暂时不支持点语法。 包含文件 ThinkPHP3.2.3完全开发手册 - 303 -© 本文档使用 看云 构建 在当前模版文件中包含其他的模版文件使用include标签,标签用法: 使用模版表达式 模版表达式的定义规则为:模块@主题/控制器/操作模块@主题/控制器/操作 例如: // 包含头部模版header // 包含菜单模版menu // 包含blue主题下面的menu模 版 可以一次包含多个模版,例如: 注意,包含模版文件并不会自动调用控制器的方法,也就是说包含的其他模 版文件中的变量赋值需要在当前操作中完成。 使用模版文件 可以直接包含一个模版文件名(包含完整路径),例如: 传入参数 ThinkPHP3.2.3完全开发手册 - 304 -© 本文档使用 看云 构建 无论你使用什么方式包含外部模板,Include标签支持在包含文件的同时传入参 数,例如,下面的例子我们在包含header模板的时候传入了title和keywords 变量: 就可以在包含的header.html文件里面使用title和keywords变量,如下: [title] 注意:由于模板解析的特点,从入口模板开始解析,如果外部模板有所更 改,模板引擎并不会重新编译模板,除非在调试模式下或者缓存已经过期。 如果部署模式下修改了包含的外部模板文件后,需要把模块的缓存目录清 空,否则无法生效。 内置标签 变量输出使用普通标签就足够了,但是要完成其他的控制、循环和判断功能,就 需要借助模板引擎的标签库功能了,系统内置标签库的所有标签无需引入标签库 即可直接使用。 内置标签包括: 标签名标签名 作用作用 包含属性包含属性 ThinkPHP3.2.3完全开发手册 - 305 -© 本文档使用 看云 构建 include 包含外部模板文件 (闭合) file import 导入资源文件(闭合 包括js css load别 名) file,href,type,value,basepath volist 循环数组数据输出 name,id,offset,length,key,mod foreach 数组或对象遍历输出 name,item,key for For循环数据输出 name,from,to,before,step switch 分支判断输出 name case 分支判断输出(必须 和switch配套使用) value,break default 默认情况输出(闭合 必须和switch配套使 用) 无 compare 比较输出(包括eq neq lt gt egt elt heq nheq等别名) name,value,type range 范围判断输出(包括 in notin between notbetween别名) name,value,type present 判断是否赋值 name notpresent 判断是否尚未赋值 name empty 判断数据是否为空 name notempty 判断数据是否不为空 name defined 判断常量是否定义 name notdefined 判断常量是否未定义 name 标签名标签名 作用作用 包含属性包含属性 ThinkPHP3.2.3完全开发手册 - 306 -© 本文档使用 看云 构建 define 常量定义(闭合) name,value assign 变量赋值(闭合) name,value if 条件判断输出 condition elseif 条件判断输出(闭合 必须和if标签配套使 用) condition else 条件不成立输出(闭 合 可用于其他标签) 无 php 使用php代码 无 标签名标签名 作用作用 包含属性包含属性 Volist标签 volist标签通常用于查询数据集(select方法)的结果输出,通常模型的select 方法返回的结果是一个二维数组,可以直接使用volist标签进行输出。 在控制器 中首先对模版赋值: $User = M('User'); $list = $User->limit(10)->select(); $this->assign('list',$list); 在模版定义如下,循环输出用户的编号和姓名: {$vo.id}:{$vo.name}
Volist标签的name属性表示模板赋值的变量名称,因此不可随意在模板文件中 ThinkPHP3.2.3完全开发手册 - 307 -© 本文档使用 看云 构建 改变。id表示当前的循环变量,可以随意指定,但确保不要和name属性冲突, 例如: {$data.id}:{$data.name}
支持输出查询结果中的部分数据,例如输出其中的第5~15条记录 {$vo.name} 输出偶数记录 {$vo.name} Mod属性还用于控制一定记录的换行,例如: {$vo.name}
为空的时候输出提示: {$vo.id}|{$vo.name} ThinkPHP3.2.3完全开发手册 - 308 -© 本文档使用 看云 构建 empty属性不支持直接传入html语法,但可以支持变量输出,例如: $this->assign('empty','没有数据'); $this->assign('list',$list); 然后在模板中使用: {$vo.id}|{$vo.name} 输出循环变量 {$k}.{$vo.name} 如果没有指定key属性的话,默认使用循环变量i,例如: {$i}.{$vo.name} 如果要输出数组的索引,可以直接使用key变量,和循环变量不同的是,这个 key是由数据本身决定,而不是循环控制的,例如: {$key}.{$vo.name} 模板中可以直接使用函数设定数据集,而不需要在控制器中给模板变量赋值传入 ThinkPHP3.2.3完全开发手册 - 309 -© 本文档使用 看云 构建 数据集变量,如: {$vo.name} Foreach标签 foreach标签类似与volist标签,只是更加简单,没有太多额外的属性,例如: {$vo.id}:{$vo.name} name表示数据源 item表示循环变量。 可以输出索引,如下: {$key}|{$vo} 也可以定义索引的变量名 {$k}|{$vo} ThinkPHP3.2.3完全开发手册 - 310 -© 本文档使用 看云 构建 For标签 用法: 开始值、结束值、步进值和循环变量都可以支持变量,开始值和结束值是必须, 其他是可选。comparison 的默认值是lt;name的默认值是i,步进值的默认值 是1,举例如下: {$i} 解析后的代码是 for ($i=1;$i<100;$i+=1){ echo $i; } Switch标签 用法: ThinkPHP3.2.3完全开发手册 - 311 -© 本文档使用 看云 构建 输出内容1 输出内容2 默认情况 使用方法如下: value1 value2 default 其中name属性可以使用函数以及系统变量,例如: admin default 对于case的value属性可以支持多个条件的判断,使用”|”进行分割,例如: 图像格式 其他格式 表示如果$_GET["type"] 是gif、png或者jpg的话,就判断为图像格式。 Case标签还有一个break属性,表示是否需要break,默认是会自动添加 break,如果不要break,可以使用: ThinkPHP3.2.3完全开发手册 - 312 -© 本文档使用 看云 构建 admin admin default 也可以对case的value属性使用变量,例如: admin member default 使用变量方式的情况下,不再支持多个条件的同时判断。 比较标签 比较标签用于简单的变量比较,复杂的判断条件可以用if标签替换,比较标签是 一组标签的集合,基本上用法都一致,如下: <比较标签 name="变量" value="值"> 内容 系统支持的比较标签以及所表示的含义分别是: 标签标签 含义含义 eq或者 equal 等于 ThinkPHP3.2.3完全开发手册 - 313 -© 本文档使用 看云 构建 neq 或者notequal 不等于 gt 大于 egt 大于等于 lt 小于 elt 小于等于 heq 恒等于 nheq 不恒等于 标签标签 含义含义 他们的用法基本是一致的,区别在于判断的条件不同,并且所有的比较标签都可 以和else标签一起使用。 例如,要求name变量的值等于value就输出,可以使用: value 或者 value 也可以支持和else标签混合使用: 相等 不相等 当 name变量的值大于5就输出 ThinkPHP3.2.3完全开发手册 - 314 -© 本文档使用 看云 构建 value 当name变量的值不小于5就输出 value 比较标签中的变量可以支持对象的属性或者数组,甚至可以是系统变量,例如: 当vo对象的属性(或者数组,或者自动判断)等于5就输出 {$vo.name} 当vo对象的属性等于5就输出 {$vo.name} 当$vo['name']等于5就输出 {$vo.name} 而且还可以支持对变量使用函数 当vo对象的属性值的字符串长度等于5就输出 {$vo.name} 变量名可以支持系统变量的方式,例如: ThinkPHP3.2.3完全开发手册 - 315 -© 本文档使用 看云 构建 相等不相等 通常比较标签的值是一个字符串或者数字,如果需要使用变量,只需要在前面添 加“$”标志: 当vo对象的属性等于$a就输出 {$vo.name} 所有的比较标签可以统一使用compare标签(其实所有的比较标签都是 compare标签的别名),例如: 当name变量的值等于5就输出 value 等效于 value 其中type属性的值就是上面列出的比较标签名称 范围判断标签 范围判断标签包括in notin between notbetween四个标签,都用于判断变量 是否中某个范围。 IN和NOTIN 用法: 假设我们中控制器中给id赋值为1: ThinkPHP3.2.3完全开发手册 - 316 -© 本文档使用 看云 构建 $id = 1; $this->assign('id',$id); 我们可以使用in标签来判断模板变量是否在某个范围内,例如: id在范围内 最后会输出: id在范围内 。 如果判断不在某个范围内,可以使用: id不在范围内 可以把上面两个标签合并 成为: id在范围内 id不在范围内 name属性还可以支持直接判断系统变量,例如: $_GET['id'] 在范围内 更多的系统变量用法可以参考系统变量部分。 value属性也可以使用变量,例如: ThinkPHP3.2.3完全开发手册 - 317 -© 本文档使用 看云 构建 id在范围内 $range变量可以是数组,也可以是以逗号分隔的字符串。 value属性还可以使用系统变量,例如: id在范围内 BETWEEN 和 NOTBETWEEN 可以使用between标签来判断变量是否在某个区间范围内,可以使用: 输出内容1 同样,可以使用notbetween标签来判断变量不在某个范围内: 输出内容2 也可以使用else标签把两个用法合并,例如: ThinkPHP3.2.3完全开发手册 - 318 -© 本文档使用 看云 构建 输出内容1 输出内容2 当使用between标签的时候,value只需要一个区间范围,也就是只支持两个 值,后面的值无效,例如 输出内容1 实际判断的范围区间是 1~3 ,而不是 1~10 ,也可以支持字符串判断,例如: 输出内容1 name属性可以直接使用系统变量,例如: 输出内容1 value属性也可以使用变量,例如: 输出内容1 ThinkPHP3.2.3完全开发手册 - 319 -© 本文档使用 看云 构建 变量的值可以是字符串或者数组,还可以支持系统变量。 输出内容1 RANGE 也可以直接使用range标签,替换前面的判断用法: 输出内容1 其中type属性的值可以用in/notin/between/notbetween,其它属性的用法 和IN或者BETWEEN一致。 IF标签 用法示例: value1 value2 value3 在condition属性中可以支持eq等判断表达式,同上面的比较标签,但是不支持 带有”>”、”<”等符号的用法,因为会混淆模板解析,所以下面的用法是错 误的: ThinkPHP3.2.3完全开发手册 - 320 -© 本文档使用 看云 构建 value1 value2 必须改成: value1 value2 除此之外,我们可以在condition属性里面使用php代码,例如: Think PHP other Framework condition属性可以支持点语法和对象语法,例如: 自动判断user变量是数组 还是对象 ThinkPHP other Framework 或者知道user变量是对象 ThinkPHP other Framework 由于if标签的condition属性里面基本上使用的是php语法,尽可能使用判断标判断标 ThinkPHP3.2.3完全开发手册 - 321 -© 本文档使用 看云 构建 签和Switch标签签和Switch标签会更加简洁,原则上来说,能够用switch和比较标签解决的尽 量不用if标签完成。因为switch和比较标签可以使用变量调节器和系统变量。如 果某些特殊的要求下面,IF标签仍然无法满足要求的话,可以使用原生php代码 或者PHP标签来直接书写代码。 Present标签 present标签用于判断某个变量是否已经定义,用法: name已经赋值 如果判断没有赋值,可以使用: name还没有赋值 可以把上面两个标签合并成为: name已经赋值 name还没有赋值 present标签的name属性可以直接使用系统变量,例如: ThinkPHP3.2.3完全开发手册 - 322 -© 本文档使用 看云 构建 $_GET['name']已经赋值 Empty标签 empty标签用于判断某个变量是否为空,用法: name为空值 如果判断没有赋值,可以使用: name不为空 可以把上面两个标签合并成为: name为空 name不为空 name属性可以直接使用系统变量,例如: ThinkPHP3.2.3完全开发手册 - 323 -© 本文档使用 看云 构建 $_GET['name']为空值 Defined标签 DEFINED标签用于判断某个常量是否有定义,用法如下: NAME常量已经定义 name属性的值要注意严格大小写 如果判断没有被定义,可以使用: NAME常量未定义 可以把上面两个标签合并成为: NAME常量已经定义 NAME常量未定义 ThinkPHP3.2.3完全开发手册 - 324 -© 本文档使用 看云 构建 Assign标签 ASSIGN标签用于在模板文件中赋值变量,用法如下: 在运行模板的时候,赋值了一个 var 的变量,值是 123 。 name属性支持系统变量,例如: 表示在模板中给 $_GET['id'] 赋值了 123 value属性也支持变量,例如: 或者直接把系统变量赋值给var变量,例如: 相当于,执行了: $var = $_GET['name']; Define标签 DEFINE标签用于中模板中定义常量,用法如下: ThinkPHP3.2.3完全开发手册 - 325 -© 本文档使用 看云 构建 在运行模板的时候,就会定义一个 MY_DEFINE_NAME 的常量。 value属性可以支持变量(包括系统变量),例如: 或者 标签嵌套 模板引擎支持标签的多层嵌套功能,可以对标签库的标签指定可以嵌套。 系统内置的标签中,volist、switch、if、elseif、else、foreach、 compare(包括所有的比较标签)、(not)present、(not)empty、 (not)defined等标签都可以嵌套使用。例如: {$sub.name} 上面的标签可以用于输出双重循环。 嵌套层次是由标签库中的标签定义的时候的level属性决定的。 ThinkPHP3.2.3完全开发手册 - 326 -© 本文档使用 看云 构建 import标签 传统方式的导入外部JS和CSS文件的方法是直接在模板文件使用: