thinkphp参考

 

1.简介
ThinkPHP是一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架,遵循Apache2开源协议发布,是为了敏捷WEB应用开发和简化企业级应用开发而诞生的。拥有众多的优秀功能和特性,经历了三年多发展的同时,在社区团队的积极参与下,在易用性、扩展性和性能方面不断优化和改进,众多的典型案例确保可以稳定用于商业以及门户级的开发。
ThinkPHP借鉴了国外很多优秀的框架和模式,使用面向对象的开发结构和MVC模式,采用单一入口模式等,融合了Struts的Action思想和JSP的TagLib(标签库)、RoR的ORM映射和ActiveRecord模式,封装了CURD和一些常用操作,在项目配置、类库导入、模版引擎、查询语言、自动验证、视图模型、项目编译、缓存机制、SEO支持、分布式数据库、多数据库连接和切换、认证机制和扩展性方面均有独特的表现。
使用ThinkPHP,你可以更方便和快捷的开发和部署应用。当然不仅仅是企业级应用,任何PHP应用开发都可以从ThinkPHP的简单和快速的特性中受益。ThinkPHP本身具有很多的原创特性,并且倡导大道至简,开发由我的开发理念,用最少的代码完成更多的功能,宗旨就是让WEB应用开发更简单、更快速。为此ThinkPHP会不断吸收和融入更好的技术以保证其新鲜和活力,提供WEB应用开发的最佳实践!
ThinkPHP遵循Apache2开源许可协议发布,意味着你可以免费使用ThinkPHP,甚至允许把你基于ThinkPHP开发的应用开源或商业产品发布/销售。

***2.入门基础

***2.1基础概念
在学习和掌握ThinkPHP开发之前,我们有必要了解一些相关的基础概念,这样会更加便于后面内容的理解和掌握。
***2.1.1LAMP
LAMP是基于Linux,Apache,MySQL和PHP的开放资源网络开发平台,PHP是一种有时候用Perl或Python可代替的编程语言。这个术语来自欧洲,在那里这些程序常用来作为一种标准开发环境。名字来源于每个程序的第一个字母。每个程序在所有权里都符合开放源代码标准:Linux是开放系统;Apache是最通用的网络服务器;MySQL是带有基于网络管理附加工具的关系数据库;PHP是流行的对象脚本语言,它包含了多数其它语言的优秀特征来使得它的网络开发更加有效。开发者在Windows操作系统下使用这些Linux环境里的工具称为使用WAMP。
虽然这些开放源代码程序本身并不是专门设计成同另外几个程序一起工作的,但由于它们都是影响较大的开源软件,拥有很多共同特点,这就导致了这些组件经常在一起使用。在过去的几年里,这些组件的兼容性不断完善,在一起的应用情形变得更加普遍。并且它们为了改善不同组件之间的协作,已经创建了某些扩展功能。目前,几乎在所有的Linux发布版中都默认包含了这些产品。Linux操作系统、Apache服务器、MySQL数据库和Perl、PHP或者 Python语言,这些产品共同组成了一个强大的Web应用程序平台。
随着开源潮流的蓬勃发展,开放源代码的LAMP已经与J2EE和.Net商业软件形成三足鼎立之势,并且该软件开发的项目在软件方面的投资成本较低,因此受到整个IT界的关注。从网站的流量上来说,70%以上的访问流量是LAMP来提供的,LAMP是最强大的网站解决方案.

***2.1.1OOP
面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是一种计算机编程架构。OOP 的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成。OOP 达到了软件工程的三个主要目标:重用性、灵活性和扩展性。为了实现整体运算,每个对象都能够接收信息、处理数据和向其它对象发送信息。OOP 主要有以下的概念和组件:
组件 - 数据和功能一起在运行着的计算机程序中形成的单元,组件在 OOP 计算机程序中是模块和结构化的基础。
抽象性 - 程序有能力忽略正在处理中信息的某些方面,即对信息主要方面关注的能力。
封装 - 也叫做信息封装:确保组件不会以不可预期的方式改变其它组件的内部状态;只有在那些提供了内部状态改变方法的组件中,才可以访问其内部状态。每类组件都提供了一个与其它组件联系的接口,并规定了其它组件进行调用的方法。
多态性 - 组件的引用和类集会涉及到其它许多不同类型的组件,而且引用组件所产生的结果得依据实际调用的类型。
继承性 - 允许在现存的组件基础上创建子类组件,这统一并增强了多态性和封装性。典型地来说就是用类来对组件进行分组,而且还可以定义新类为现存的类的扩展,这样就可以将类组织成树形或网状结构,这体现了动作的通用性。
由于抽象性、封装性、重用性以及便于使用等方面的原因,以组件为基础的编程在脚本语言中已经变得特别流行。
***2.1.3MVC
MVC是一个设计模式,它强制性的使应用程序的输入、处理和输出分开。使用MVC应用程序被分成三个核心部件:模型(M)、视图(V)、控制器(C),它们各自处理自己的任务。
视图 :视图是用户看到并与之交互的界面。对老式的Web应用程序来说,视图就是由HTML元素组成的界面,在新式的Web应用程序中,HTML依旧在视图中扮演着重要的角色,但一些新的技术已层出不穷,它们包括Adobe Flash和象XHTML,XML/XSL,WML等一些标识语言和Web services。如何处理应用程序的界面变得越来越有挑战性。MVC一个大的好处是它能为你的应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,不管这些数据是联机存储的还是一个雇员列表,作为视图来讲,它只是作为一种输出数据并允许用户操纵的方式。
模型 :模型表示企业数据和业务规则。在MVC的三个部件中,模型拥有最多的处理任务。例如它可能用象EJBs和ColdFusion Components这样的构件对象来处理数据库。被模型返回的数据是中立的,就是说模型与数据格式无关,这样一个模型能为多个视图提供数据。由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。
控制器 :控制器接受用户的输入并调用模型和视图去完成用户的需求。所以当单击Web页面中的超链接和发送HTML表单时,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后确定用哪个视图来显示模型处理返回的数据。
现在我们总结MVC的处理过程,首先控制器接收用户的请求,并决定应该调用哪个模型来进行处理,然后模型用业务逻辑来处理用户的请求并返回数据,最后控制器用相应的视图格式化模型返回的数据,并通过表示层呈现给用户。
***2.1.4ORM
对象-关系映射(Object/Relation Mapping,简称ORM),是随着面向对象的软件开发方法发展而产生的。面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射(ORM)系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。
面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为了解决这个不匹配的现象,对象关系映射技术应运而生。
***2.1.5CURD
CURD是一个数据库技术中的缩写词,一般的项目开发的各种参数的基本功能都是CURD。它代表创建(Create)、更新(Update)、读取(Read)和删除(Delete)操作。CURD 定义了用于处理数据的基本原子操作。之所以将CURD 提升到一个技术难题的高度是因为完成一个涉及在多个数据库系统中进行CURD操作的汇总相关的活动,其性能可能会随数据关系的变化而有非常大的差异。
CURD在具体的应用中并非一定使用create、update 、read和delete字样的方法,但是他们完成的功能是一致的。例如,ThinkPHP就是使用add、save、select和delete方法表示模型的CURD操作。

***2.1.6ActiveRecord
ActiveRecord也属于ORM层,由Rails最早提出,遵循标准的ORM模型:表映射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而且简洁易懂。
ActiveRecord的主要思想是:
1. 每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录;通常表的每个字段在类中都有相应的Field;
2. ActiveRecord同时负责把自己持久化,在ActiveRecord中封装了对数据库的访问,即CURD;;
3. ActiveRecord是一种领域模型(Domain Model),封装了部分业务逻辑;
ActiveRecord比较适用于:
1. 业务逻辑比较简单,当你的类基本上和数据库中的表一一对应时, ActiveRecord是非常方便的,即你的业务逻辑大多数是对单表操作;
2. 当发生跨表的操作时, 往往会配合使用事务脚本(Transaction Script),把跨表事务提升到事务脚本中;
3. ActiveRecord最大优点是简单, 直观。 一个类就包括了数据访问和业务逻辑. 如果配合代码生成器使用就更方便了;
这些优点使ActiveRecord特别适合WEB快速开发。

***2.1.7单一入口
单一入口通常是指一个项目或者应用具有一个统一(但并不一定是唯一)的入口文件,也就是说项目的所有功能操作都是通过这个入口文件进行的,并且往往入口文件是第一步被执行的。
单一入口的好处是项目整体比较规范,因为同一个入口,往往其不同操作之间具有相同的规则。另外一个方面就是单一入口带来的好处是控制较为灵活,因为拦截方便了,类似如一些权限控制、用户登录方面的判断和操作可以统一处理了。
或者有些人会担心所有网站都通过一个入口文件进行访问,是否会造成太大的压力,其实这是杞人忧天的想法。
***2.2获取ThinkPHP

获取ThinkPHP的方式很多,官方网站(http://thinkphp.cn)是最好的下载和文档获取来源。
最新的下载版本可以在http://thinkphp.cn/Down下载到。
你还还可以通过SVN获取最新的更新版本。
SVN地址:
完整版本http://thinkphp.googlecode.com/svn/trunk
核心版本http://thinkphp.googlecode.com/svn/trunk/ThinkPHP 
更多的ThinkPHP相关资源:
Google项目地址:http://code.google.com/p/thinkphp/
SF项目地址:http://sourceforge.net/projects/thinkphp
ThinkPHP无需任何安装,直接拷贝到你的电脑或者服务器目录下面即可。没有入口文件的调用,ThinkPHP不会执行任何操作。

***2.3关于版本

本完全手册的内容主要针对最新的ThinkPHP 2.0版本,尽管有些功能在之前的版本上面也能使用,但是我们不建议在使用1.5版本或者更早版本开发的过程中参考(事实上,1.*以后的每个发布版本都有详细的文档)。2.*版本的体系架构和2.0版本是保持一致的,因此对于以后的2.*版本,本手册中涉及的内容基本上可以适用。如有变更,会在最新的发布版本中注明。

***2.4环境要求

ThinkPHP可以支持Windows/Unix服务器环境,可运行于包括Apache、IIS和nginx在内的多种WEB服务器和模式,需要PHP5.0以上版本支持,支持Mysql、MsSQL、PgSQL、Sqlite、Oracle、Ibase以及PDO等多种数据库和连接。框架本身没有什么特别模块要求,具体的应用系统运行环境要求视开发所涉及的模块。ThinkPHP底层运行的内存消耗极低,而本身的文件大小也是轻量级的,因此不会出现空间和内存占用的瓶颈。
对于刚刚接触PHP或者ThinkPHP的新手,我们推荐使用集成开发环境WAMPServer(http://www.wampserver.com/en/ 是一个集成了Apache、PHP和MySQL的开发套件,而且可以支持不同PHP版本的切换)来使用ThinkPHP进行本地开发和测试。

***2.5许可协议

ThinkPHP遵循Apache2开源协议发布。Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再作为开源或商业软件发布。需要满足的条件:
1. 需要给代码的用户一份Apache Licence;
2. 如果你修改了代码,需要在被修改的文件中说明;
3. 在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议,商标,专利声明和其他原来作者规定需要包含的说明;
4. 如果再发布的产品中包含一个Notice文件,则在Notice文件中需要带有Apache Licence。你可以在Notice中增加自己的许可,但不可以表现为对Apache Licence构成更改。
具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0

 
***3 架构设计

***3.1系统特性
ThinkPHP是一个性能卓越并且功能丰富的轻量级PHP开发框架,本身具有很多的原创特性,并且倡导大道至简,开发由我的开发理念,用最少的代码完成更多的功能,宗旨就是让WEB应用开发更简单、更快速。从1.*版本开始就放弃了对PHP4的兼容,因此整个框架的架构和实现能够得以更加灵活和简单。2.0版本更是在之前的基础上,经过全新的重构和无数次的完善以及改进,达到了一个新的阶段,足以达到企业级和门户级的开发标准。
ThinkPHP值得推荐的特性包括:
 类库导入:ThinkPHP是首先采用基于类库包和命名空间的方式导入类库,让类库导入看起来更加简单清晰,而且还支持冲突检测和别名导入。为了方便项目的跨平台移植,系统还可以严格检查加载文件的大小写。
 URL模式:系统支持普通模式、PATHINFO模式、REWRITE模式和兼容模式的URL方式,支持不同的服务器和运行模式的部署,配合URL路由功能,让你随心所欲的构建需要的URL地址和进行SEO优化工作。
 编译机制:独创的核心编译和项目的动态编译机制,有效减少OOP开发中文件加载的性能开销。ALLINONE模式更是让你体验飞一般的感觉。
 ORM:简洁轻巧的ORM实现,配合简单的CURD以及AR模式,让开发效率无处不在。
 查询语言:内建丰富的查询机制,包括组合查询、复合查询、区间查询、统计查询、定位查询、动态查询和原生查询,让你的数据查询简洁高效。
 动态模型:无需创建任何对应的模型类,轻松完成CURD操作,支持多种模型之间的动态切换,让你领略数据操作的无比畅快和最佳体验。
 高级模型:可以轻松支持序列化字段、文本字段、只读字段、延迟写入、乐观锁、数据分表等高级特性。
 视图模型:轻松动态地创建数据库视图,多表查询不再烦恼。
 关联模型:让你以出乎意料的简单、灵活的方式完成多表的关联操作。
 分组模块:不用担心大项目的分工协调和部署问题,分组模块帮你解决跨项目的难题。
 模板引擎:系统内建了一款卓越的基于XML的编译型模板引擎,支持两种类型的模板标签,融合了Smarty和JSP标签库的思想,支持标签库扩展。通过驱动还可以支持Smarty、EaseTemplate、TemplateLite、Smart等第三方模板引擎。
 AJAX支持:内置AJAX数据返回方法,支持JSON、XML和EVAL格式返回客户端,并且系统不绑定任何AJAX类库,可随意使用自己熟悉的AJAX类库进行操作。
 多语言支持:系统支持语言包功能,项目和模块都可以有单独的语言包,并且可以自动检测浏览器语言自动载入对应的语言包。
 模式扩展:除了标准模式外,系统内置了Lite、Thin和Cli模式,针对不同级别的应用开发提供最佳核心框架,还可以自定义模式扩展。
 自动验证和完成:自动完成表单数据的验证和过滤,生成安全的数据对象。
 字段类型检测:字段类型强制转换,确保数据写入和查询更安全。
 数据库特性:系统支持多数据库连接和动态切换机制,支持分布式数据库。犹如企业开发的一把利刃,跨数据库应用和分布式支持从此无忧。
 缓存机制:系统支持包括文件方式、APC、Db、Memcache、Shmop、Eaccelerator和Xcache在内的多种动态数据缓存类型,以及可定制的静态缓存规则,并提供了快捷方法进行存取操作。
扩展机制:系统支持包括类库扩展、驱动扩展、应用扩展、模型扩展、控制器扩展、标签库扩展、模板引擎扩展、Widget扩展、行为扩展和模式扩展在内的强大灵活的扩展机制,让你不再受限于核心的不足和无所适从,随心DIY自己的框架和扩展应用。
***3.2目录结构

新版的目录结构在原来的基础上进行了调整,更加清晰。
一、系统目录(ThinkPHP框架目录)
ThinkPHP.php框架的公共入口文件
Common包含框架的一些公共文件、系统定义、系统函数和惯例配置等
Lang系统语言文件
Lib系统基类库目录
Tpl系统模板目录
Mode框架模式扩展目录
Vendor第三方类库目录
 
二、应用目录(项目目录)
index.php项目入口文件(可以使用其他名称或者放置于其他位置)
Common项目公共文件目录,一般放置项目的公共函数
Conf项目配置目录,所有的配置文件都放在这里。
Lang项目语言包目录(可选)
Lib项目类库目录,通常包括Action和Model子目录
Tpl项目模板目录,支持模板主题
Runtime项目运行时目录,包括Cache(模板缓存)、Temp(数据缓存)、Data(数据目录)和Logs(日志文件)子目录
上面的只是默认方式,项目下面的目录名称和结构是可以重新定义的。其实项目目录并不需要开发人员手动创建,只需要定义好项目的入口文件之后,系统会在第一次执行的时候自动生成项目必须的所有目录结构(前提是项目目录具有可写权限,这点在Linux环境下面需要注意)。
可以看出新版的目录结构更加便于部署和配置,因为只有Runtime目录才是需要具备可写权限的,在Linux环境下面可以更加快速的部署和配置目录权限。
 
三、部署目录
当我们实际部署网站的时候,目录结构往往由于项目的复杂而变得复杂。我们推荐的部署目录结构如下:
ThinkPHP系统目录(下面的目录结构同上面的系统目录)
Home项目目录(下面的目录结构同上面的应用目录)
Admin后台管理项目目录
……更多的项目目录
index.php网站的入口文件
admin.php网站的后台入口文件
 
如果采用分组模块的话 可以简化为一个项目目录
ThinkPHP系统目录(下面的目录结构同上面的系统目录)
App项目目录
Public网站公共目录
index.php网站的入口文件
 
项目的模板文件还是放到项目的Tpl目录下面,只是将外部调用的资源文件, 包括图片 JS和CSS统一放到网站的公共目录Public下面,分Images、Js和Css子目录存放,如果有可能的话,甚至也可以把这些资源文件单独放一个外部的服务器远程调用,并进行优化。
这样部署的好处是系统目录和项目目录可以放到非WEB访问目录下面,网站目录下面可以只需要放置Public公共目录和index.php入口文件(如果是多个项目的话,每个项目的入口文件都需要放到WEB目录下面),从而提高网站的安全性。

***3.3MVC分层

MVC是一种将应用程序的逻辑层和表现层进行分离的方法。ThinkPHP也是基于MVC设计模式的。MVC只是一个抽象的概念,并没有特别明确的规定,ThinkPHP中的MVC分层大致体现在:
模型(M):模型的定义由Model类来完成。
控制器(C):应用控制器(核心控制器App类)和Action控制器都承担了控制器的角色,Action控制器完成业务过程控制,而应用控制器负责调度控制。
视图(V):由View类和模板文件组成,模板做到了100%分离,可以独立预览和制作。
有些时候,ThinkPHP并不依赖M或者V,也就是说没有模型或者视图也一样可以工作。甚至也不依赖C,这是因为ThinkPHP在Action之上还有一个总控制器,即App控制器,负责应用的总调度。在没有C的情况下,必然存在视图V,否则就不再是一个完整的应用。
总而言之,ThinkPHP的MVC模式只是提供了一种敏捷开发的手段,而不是拘泥于MVC本身。

***3.4执行流程
基于ThinkPHP框架的应用程序组成和执行过程,如图所示:(略)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

***3.5命名规范
框架必然有其自身的一定规范,在ThinkPHP中亦然。下面是使用ThinkPHP应该尽量遵循的命名规范:
 类文件都是以.class.php为后缀(这里是指的ThinkPHP内部使用的类库文件,不代表外部加载的类库文件),使用驼峰法命名,并且首字母大写,例如DbMysql.class.php。
 函数、配置文件等其他类库文件之外的一般是以.php为后缀(第三方引入的不做要求)。
 确保文件的命名和调用大小写一致,是由于在类Unix系统上面,对大小写是敏感的(而ThinkPHP在调试模式下面,即使在Windows平台也会严格检查大小写)。
 类名和文件名一致(包括上面说的大小写一致),例如 UserAction类的文件命名是UserAction.class.php, InfoModel类的文件名是InfoModel.class.php,
 函数的命名使用小写字母和下划线的方式,例如 get_client_ip
 Action控制器类以Action为后缀,例如 UserAction、InfoAction
 模型类以Model为后缀,例如UserModel、InfoModel
 方法的命名使用驼峰法,并且首字母小写,例如 getUserName
 属性的命名使用驼峰法,并且首字母小写,例如 tableName
 以双下划线“__”打头的函数或方法作为魔法方法,例如 __call和 __autoload
 常量以大写字母和下划线命名,例如 HAS_ONE和 MANY_TO_MANY
 配置参数以大写字母和下划线命名,例如HTML_CACHE_ON
 语言变量以大写字母和下划线命名,例如MY_LANG,以下划线打头的语言变量通常用于系统语言变量,例如 _CLASS_NOT_EXIST_。
 数据表和字段采用小写加下划线方式命名,例如 think_user和 user_name
特例:
在ThinkPHP里面,有一个函数命名的特例,就是单字母大写函数,这类函数通常是某些操作的快捷定义,或者有特殊的作用。例如,ADSL方法等等,他们有着特殊的含义,后面会有所了解。
另外一点,ThinkPHP默认使用UTF-8编码,所以请确保你的程序文件采用UTF-8编码格式保存,并且去掉BOM信息头(去掉BOM头信息有很多方式,不同的编辑器都有设置方法,也可以用工具进行统一检测和处理)。

***3.6入口文件
ThinkPHP采用单一入口模式进行项目部署和访问,无论完成什么功能,一个项目只有一个统一(但不一定是唯一)的入口。并且所有的项目的入口文件是类似的,入口文件主要完成的作用是:
 路径定义 项目名称定义(可选)
 额外参数定义(可选)
 载入框架入口文件(必须)
 实例化一个App应用(必须)
 
下面是一个标准的入口文件的写法:
<?php
// 定义ThinkPHP框架路径(相对于入口文件)
define(‘THINK_PATH’, ‘../ThinkPHP’);
//定义项目名称和路径
define(‘APP_NAME’, ‘Myapp’);
define(‘APP_PATH’, ‘.’);
// 加载框架入口文件
require(THINK_PATH.”/ThinkPHP.php”);
//实例化一个网站应用实例
App::run();
?>

***3.7项目编译
ThinkPHP正式版本开始引入了新的项目编译机制,所谓的项目编译机制是指系统第一次运行的时候会自动生成核心缓存文件~runtime.php和项目编译缓存文件~app.php,这些编译缓存文件把核心和项目必须的文件打包到一个文件中,并且去掉所有空白和注释代码,因为存在一个预编译的过程,所以还会进行一些相关的目录检测,对于不存在的目录可以自动生成,这个自动生成机制后面还会提到。当第二次执行的时候就会直接载入编译过的缓存文件,从而省去很多IO开销,加快执行速度。项目编译机制对运行没有任何影响,预编译操作和其他的目录检测机制只会执行一次,因此无论在预编译过程中做了多少复杂的操作,对后面的执行没有任何效率的缺失。
编译缓存文件,默认是自动生成在项目目录下面的Runtime目录下面。如果希望自己设置目录,可以在入口文件里面设置RUNTIME_PATH进行更改,例如
define(‘RUNTIME_PATH’,’./MyApp/temp/’);
注意在Linux环境下面需要对RUNTIME_PATH目录设置可写权限。
核心编译缓存文件~runtime.php包含的文件由系统的core.php文件决定,如果是采用了模式扩展的话,就由模式扩展入口文件决定。默认的核心模式下面包含了下面的一些文件:系统定义文件defines.php、系统函数库functions.php、系统基类Think、异常基类ThinkException、日志类 Log、应用类 App、控制器基类 Action、视图类 View。
其他类库可以在操作方法中使用系统导入机制或者自动加载机制完成加载。
项目编译缓存文件~app.php通常包含了下面的一些文件:项目配置文件(由惯例配置、项目配置合并而成)、项目公共函数文件common.php。每个项目还可以单独添加自己的项目编译文件列表,只需要在项目配置目录下面定义app.php文件,返回需要额外添加到项目编译缓存的文件列表数组即可。
注意在调试模式下面不会生成项目编译缓存,但是依然会生成核心缓存。如果不希望生成核心缓存文件的话,可以在项目入口文件里面设置NO_CACHE_RUNTIME,例如:
define(‘NO_CACHE_RUNTIME’,True);
以及设置对编译缓存的内容是否进行去空白和注释,例如:
define(‘STRIP_RUNTIME_SPACE’,false);
则生成的编译缓存文件是没有经过去注释和空白的,仅仅是把文件合并到一起,这样的好处是便于调试的错误定位,建议部署模式的时候把上面的设置为True或者删除该定义。

***3.8URL访问
ThinkPHP框架基于模块和操作的方式进行访问,由于ThinkPHP框架的应用采用单一入口文件来执行,因此网站的所有的模块和操作都通过URL的参数来访问和执行。这样一来,传统方式的文件入口访问会变成由URL的参数来统一解析和调度。
ThinkPHP强大的URL解析、调度以及路由功能为这个功能实现提供了有力的保证,并且可以在绝大多数的服务器环境里面部署成功。
ThinkPHP支持的URL模式包括普通模式、PATHINFO模式、REWRITE模式和兼容模式,并且都提供路由支持。默认为PATHINFO模式,提供最好的用户体验和搜索引擎友好支持。
例如普通模式下面的URL为:
http://localhost/appName/index.php?m=moduleName&a=actionName&id=1
如果使用PATHINFO模式的话,URL成为:
http://localhost/appName/index.php/moduleName/actionName/id/1/
PATHINFO模式对以往的编程方式没有影响,GET和POST方式传值依然有效,因为系统会对PATHINFO方式自动处理,例如上面URL地址中的id的值可以通过 $_GET[‘id’]的方式正常获取到。
如果使用REWRITE模式,通过配置URL可以成为:
http://localhost/appName/moduleName/actionName/id/1/
例如上面生成的myApp项目如果我们通过下面的URL访问:
http://localhost/myApp/
其实是定位到myApp项目的Index模块的index操作,因为系统在没有指定模块和操作的时候,会执行默认的模块和操作,这个在ThinkPHP的惯例配置里面是Index模块和index操作。因此下面的URL和上面的结果是相同的:
http://localhost/myApp/index.php/Index/index/
通过项目配置参数,我们可以改变这个默认配置。
系统还支持分组模式和URL路由的功能,这些都能够带来URL的不同体验。

***3.9控制器
ThinkPHP的控制器就是模块类,通常位于项目的Lib\Action目录下面。类名就是模块名加上Action后缀,例如IndexAction类就表示了Index模块。控制器类必须继承系统的Action基础类,这样才能确保使用Action类内置的方法。而index操作其实就是IndexAction类的一个公共方法,所以我们在浏览器里面输入URL:
http://localhost/myApp/index.php/Index/index/
其实就是执行了IndexAction类的index(公共)方法。
每个模块的操作并非一定需要有定义操作方法,如果我们只是希望输出一个模板,既没有变量也没有任何的业务逻辑,那么只需要按照规则定义好操作对应的模板文件即可,而不需要定义操作方法。例如,我们在IndexAction中如果没有定义help方法,但是存在对应的Index/help.html模板文件,那么下面的URL访问依然可以正常运作:
http://localhost/myApp/index.php/Index/help/
因为系统找不到IndexAction类的help方法,会自动定位到Index模块的模板目录中查找help.html模板文件,然后直接渲染输出。
控制器中还设计了模块分组、空操作、空模块、前置和后置操作、操作链等功能,后面会有详细的描述。

***3.10模型
在ThinkPHP中基础的模型类就是Model类,该类完成了基本的CURD、ActiveRecord模式、连贯操作和统计查询,一些高级特性被封装到另外的模型类中,例如AdvModel高级模型类完成了一些包括文本字段、只读字段、序列化字段、乐观锁、多数据库连接等模型的高级特性,ViewModel视图模型类完成了模型的视图操作,RelationModel关联模型类完成了模型的关联操作。
基础模型类Model的设计非常灵活,甚至可以无需进行任何模型定义,就可以进行相关数据表的ORM和CURD操作,只有在需要封装单独的业务逻辑的时候,模型类才是必须被定义的。
新版实现了动态模型的设计,可以从基础模型类切换到其他模型类进行方法操作而不会丢失现有的数据属性。这是一个真正的按需加载的思想,而不再是必须要事先继承需要操作的模型类。

***3.11数据库抽象层
ThinkPHP内置了抽象数据库访问层,把不同的数据库操作封装起来,而使用了统一的操作接口。我们只需要使用公共的Db类进行操作,而无需针对不同的数据库写不同的代码和底层实现,Db类会自动调用相应的数据库适配器来处理。目前支持Mysql、MsSQL、PgSQL、Sqlite、Oracle、Ibase以及PDO等多种数据库和连接。
数据库抽象层也支持分布式数据库的连接,包括对等和主从方式两种的支持,而且也支持多数据库连接和切换,为企业级应用保驾护航。

***3.12视图
ThinkPHP的视图主要由View视图类和模板文件构成。视图类负责Action控制器类和模板文件之间沟通,Action类把数据通过View类传递到模板文件,而模板文件把接收到的数据转换成相应的数据格式显示。在特殊的情况下面,视图类会缓存模板文件的输出结果,这个时候缓存文件也纳入了视图层的概念之中了。
如果模板文件使用了某些模板引擎进行标签定义,而不是使用原生的PHP语法,那么在模板输出的过程中还需要引入模板解析,如果是编译型的模板引擎例如ThinkPHP内置的模板引擎和Smarty之类的,那么模板文件会有一个编译的过程,通常编译后的模板文件会生成一个编译后的模板缓存文件,第二次输出模板文件的时候就是直接输出编译后的模板缓存。如果是解释型的模板引擎,就会在每次输出模板的过程中进行解析操作。
无论如何,视图应该仅仅是进行数据的输出显示,通常在视图渲染过程是不会改变数据本身的,而只是进行格式化输出和显示。

***3.13模板引擎
ThinkPHP内置了一个基于XML的性能卓越的模板引擎 ThinkTemplate,这是一个专门为ThinkPHP服务的内置模板引擎,无论在功能或是性能还有易用性方面都比Smarty优秀。ThinkTemplate是一个使用了XML标签库技术的编译型模板引擎,使用了动态编译和缓存技术,支持两种类型的模板标签,支持PHP原生代码和模板标签的混合使用。而且支持自定义标签库,在基于内置模板引擎的基础上,扩展更多更强大更适合自己项目所使用的模板标签,任何想达到的功能皆有可能。

***3.14.1系统函数库
系统函数库位于系统的Common目录下面,函数库文件名为functions.php,该文件会在执行过程自动加载,系统函数库中的大部分方法是核心所依赖或者经常被使用的,因此系统函数库的所有函数都可以在任何时候直接使用。
除了系统函数库外,系统还内置了一个扩展函数库extend.php,供项目开发的过程中加载调用,扩展函数库中的函数通常是核心不依赖的,但却有很好的辅助作用,能够为应用开发提供进一步的方便。需要使用扩展函数库中的方法,可以直接拷贝到你的项目函数库中。

***3.14.2快捷方法
ThinkPHP为一些常用的操作定义了快捷方法,这些方法以单字母命名,具有比较容易记忆的特点。非常有意思的是,这些快捷方法的字母包含了 ADSL字母,所以我们称之为 ADSL方法,但是并不局限于 ADSL四个方法,包括下面的:
A快速实例化Action类库
B执行行为类
C配置参数存取方法
D快速实例化Model类库
F快速简单文本数据存取方法
L语言参数存取方法
M快速高性能实例化模型
R快速远程调用Action类方法
S快速缓存存取方法
U URL动态生成和重定向方法
W快速Widget输出方法
由上可知,快捷方法的命名方式,一般是以该方法所对应的符合其功能意义的英文单词首字母进行命名,至于每个快捷方法的详细使用,我们会在具体的章节中有针对的描述或者参考附录部分。

***3.14.3项目函数库
项目函数库通常位于项目的Common目录下面,文件名为common.php,该文件会在执行过程中自动加载,并且合并到项目编译统一缓存,如果使用了分组部署方式,并且该目录下存在”分组名称/function.php”文件,也会根据当前分组执行时对应进行自动加载,因此项目函数库的所有函数也都可以无需手动载入而直接使用。
***3.15类库
***3.15.1基类库
ThinkPHP框架通过基类库的概念把所有系统类库都集中在一起管理,包括ThinkPHP的核心类库。
基类库目录位于系统目录下面的Lib目录,框架内置的有Think核心类库,还可以扩展ORG、Com扩展类库。核心基类库的作用是完成框架的通用性开发而必须的基础类和常用工具类等,包含有:
Think.Core核心类库包
Think.Db数据库类库包
Think.Exception异常处理类库包
Think.Template内置模板引擎类库包
Think.Util系统工具类库包
***3.15.2扩展类库
官方网站额外提供了很多的基类库扩展,可以直接带路径拷贝类库文件到系统的基类库目录就可以使用了。例如,我们要使用扩展类库的ORG/Util/Page.class.php的话,把Page类库拷贝到系统目录下面的Lib/ORG/Util/目录即可。
目前可以支持的扩展类库包,包括ORG和Com。所有扩展类库必须放置于上面两个类库包之下管理。

***3.15.3应用类库
应用类库是指项目中自己定义或者使用的类库,这些类库也是遵循ThinkPHP的命名规范。应用类库目录位于项目目录下面的Lib目录。应用类库的范围很广,包括Action类库、Model类库或者其他的工具类库。

***3.15.4类库导入
ThinkPHP模拟了Java的类库导入机制,统一采用import方法进行类文件的加载。import方法是ThinkPHP内建的类库和文件导入方法,提供了方便和灵活的文件导入机制,完全可以替代PHP的require和include方法。例如:
import(“Think.Util.Session”);
import(“App.Model.UserModel”);
import方法具有缓存和检测机制,相同的文件不会重复导入,如果发现导入了不同的位置下面的同名类库文件,系统会提示冲突,例如:
import(“Think.Util.Array”);
import(“ORG.Util.Array”);
上面的情况导入会产生引入两个同名的Array.class.php类,即使实际上的类名可能不存在冲突,但是按照ThinkPHP的规范,类名和文件名是一致的,所以系统会抛出类名冲突的异常,并终止执行。
注意:在Unix或者Linux主机下面是区别大小写的,所以在使用import方法的时候要注意目录名和类库名称的大小写,否则会引入文件失败。
对于import方法,系统会自动识别导入类库文件的位置,ThinkPHP的约定是Think、ORG、Com包的导入以系统基类库为相对起始目录,否则就认为是项目应用类库为起始目录。
import(“Think.Util.Session”);
import(“ORG.Util.Page”);
上面两个方法分别导入了系统目录下的Lib/Think/Util/Session.class.php和Lib/ORG/Util/Page.class.php类文件。
要导入项目的应用类库文件也很简单,使用下面的方式就可以了,和导入基类库的方式看起来差不多:
import(“MyApp.Action.UserAction”);
import(“MyApp.Model.InfoModel”);
上面的方式分别表示导入MyApp项目下面的Lib/Action/UserAction.class.php和Lib/Model/InfoModel.class.php类文件。通常我们都是在当前项目里面导入所需的类库文件,所以,我们可以使用下面的方式来简化代码
import(“@.Action.UserAction”);
import(“@.Model.InfoModel”);
除了看起来简单一些外,还可以方便项目类库的移植。
如果要在当前项目下面导入其他项目的类库,必须保证两个项目的目录是平级的,否则无法使用
import(“OtherApp.Model.GroupModel”);
的方式来加载其他项目的类库。
我们知道,按照系统的规则,import方法是无法导入具有点号的类库文件的,因为点号会直接转化成斜线,例如我们定义了一个名称为User.Info.class.php的文件的话,采用:
import(“ORG.User.Info”);
方式加载的话就会出现错误,导致加载的文件不是ORG/User.Info.class.php文件,而是ORG/User/Info.class.php文件,这种情况下,我们可以使用:
import(“ORG.User#Info”);
来导入。
对于import方法,系统会自动识别导入类库文件的位置,如果是其它情况的导入,需要指定baseUrl参数,也就是import方法的第二个参数。例如,要导入当前文件所在目录下面的
RBAC/AccessDecisionManager.class.php文件,可以使用:
import(“RBAC.AccessDecisionManager”,dirname(__FILE__));

***3.15.5导入第三方类库
我们知道 ThinkPHP的基类库都是以.class.php为后缀的,这是系统内置的一个约定,当然也可以通过 import的参数来控制, 为了更加方便引入其他框架和系统的类库, 系统增加了导入第三方类库的功能, 第三方类库统一放置在系统的Vendor目录下面,并且使用vendor方法导入,其参数和 import方法是 一致的,只是默认的值有针对变化。
例如,我们把 Zend的 Filter\Dir.php放到 Vendor目录下面,这个时候 Dir文件的路径就是
Vendor\Zend\Filter\Dir.php,我们使用vendor方法导入只需要使用:
Vendor(‘Zend.Filter.Dir’);
就可以导入Dir类库了。
***3.15.6别名导入
新版ThinkPHP引入了别名导入功能,可以预先定义好相关类库的路径,在需要使用的时候根据定义的别名进行快速导入。别名导入功能已经和import方法整合,所以我们可以统一使用import方法进行导入,例如:
import(‘AdvModel’);
如果有定义AdvModel别名,则import方法会自动加载定义的别名导入。
系统默认的别名定义文件位于系统的Common\alias.php,每个模式和项目都可以定义自己的别名定义文件。

***3.15.7自动加载
在很多情况下,我们可以利用框架的自动加载功能,完成类库的加载工作,而无需我们手动导入所需要使用的类库。这些情况包括:
 系统和项目中已经定义的别名导入;
 当前项目下面的Action类库和Model类库文件;
 自动加载路径中的类库文件;
这里的自动加载路径,是指ThinkPHP的配置参数APP_AUTOLOAD_PATH所定义的路径。
APP_AUTOLOAD_PATH参数是用于设置框架的自动导入的搜索路径的,默认的配置是Think.Util.,因此才会实现自动导入Think.Util工具类库。例如,我们需要增加ORG.Util.路径作为类库搜索路径,可以使用:
‘APP_AUTOLOAD_PATH’=> ‘Think.Util.,ORG.Util.’,
多个搜索路径之间用逗号分割,并且注意定义的顺序代表了搜索的顺序。

***3.16扩展
新版在保证核心简洁高效的同时保留了足够的扩展机制,让开发人员可以更好的扩展开发以满足项目或者自身的特殊需要。
目前可以支持的扩展包括:类库扩展、模型扩展、控制器扩展、应用扩展、标签库扩展、模板引擎扩展、模式扩展、行为扩展、Widget扩展。
***4构建应用
***4.1开发流程
使用ThinkPHP创建应用的一般开发流程是:
 创建数据库和数据表;(没有数据库操作可略过)
 项目命名并创建项目入口文件;
 完成项目配置;(无需额外配置可以忽略)
 创建控制器类;
 创建模型类;(如果只是简单的模型类可以不必创建)
 创建模板文件;
 运行和调试。
 
为了顺利完成下面的操作,我们首先在数据库创建一个测试表,以MySQL为例:
CREATE TABLE `think_demo` (
  `id` int(11) unsigned NOT NULL auto_increment,
  `title` varchar(255) NOT NULL default ”,
  `content` longtext NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
***4.2入口文件
我们给项目命名为Myapp,并且在WWW目录下面创建一个Myapp目录(项目目录),并且把下载的ThinkPHP核心目录放到该目录下面。
然后在Myapp目录下面创建一个入口文件index.php,其中内容如下:
<?php
// 定义ThinkPHP框架路径
define(‘THINK_PATH’, ‘./ThinkPHP/’);
//定义项目名称和路径
define(‘APP_NAME’, ‘Myapp’);
define(‘APP_PATH’, ‘.’);
// 加载框架入口文件
require(THINK_PATH.”/ThinkPHP.php”);
//实例化一个网站应用实例
App::run();
?>
注意,APP_PATH的路径指的是项目目录所在路径,而不是项目入口文件所在的路径。APP_NAME通常都必须和项目目录名称一致。
如果你的项目入口文件放到项目目录下面的话,可以无需定义APP_NAME和APP_PATH,系统可以自动识别。THINK_PATH通常也不是必须的。
因为我们的入口文件位于项目目录下面,因此,上面的入口文件可以简化为:
<?php
// 加载框架入口文件
require(” ./ThinkPHP/ThinkPHP.php”);
//实例化一个网站应用实例
App::run();
?>

***4.3自动生成
ThinkPHP具备项目目录自动生成功能,并且不需要使用任何命令行工具。我们只需要简单的浏览器里面访问刚才创建的应用入口文件。
打开浏览器,访问该项目的入口文件:http://127.0.0.1/Myapp/index.php
这时可以看到项目构建成功后的提示画面,并且在Myapp目录下,已为您构建好了项目目录。
 
注意:ThinkPHP框架的所有文件都是采用UTF-8编码保存,但是这不影响你的项目中使用其他编码开发和浏览。请注意确保文件保存的时候去掉UTF-8的BOM头信息,防止因产生隐藏的输出而导致程序运行不正常。
注意:如果你是在Linux环境下,要确保项目目录的自动生成,请设置Myapp目录的权限为可写,否则请自行创建相关目录。然后设置Runtime目录为可写权限(通常都是设置目录属性为777)。

***4.4项目配置
自动生成的项目目录下面已经为我们创建了一个空的项目配置文件,位于项目的Conf目录下面,名称是config.php。我们打开这个配置文件,加入我们的数据库配置信息。
<?php
return array(
 ‘APP_DEBUG’ => true,//开启调试模式
‘DB_TYPE’=> ‘mysql’,          //数据库类型
‘DB_HOST’=> ‘localhost’,//数据库服务器地址
‘DB_NAME’=>’demo’,  //数据库名称
‘DB_USER’=>’root’, //数据库用户名
‘DB_PWD’=>”, //数据库密码
‘DB_PORT’=>’3306′,//数据库端口
‘DB_PREFIX’=>’think_’,//数据表前缀
);?>
根据你本地的数据库连接信息修改上面的配置内容,修改完成后,保存项目配置文件。

***4.5业务逻辑
接下来,我们需要实现一个数据添加和查询操作的简单应用,来领略下ThinkPHP的快速开发。
在项目的Lib\Action目录下面找到自动生成的IndexAction.class.php文件,这个文件就是ThinkPHP的控制器,也就是Index模块的实现。删除IndexAction类默认生成的index方法。添加新的insert方法和index方法,代码如下:
//数据写入操作
public function insert() {
$Demo = new Model(‘Demo’);   //实例化模型类
$Demo->Create(); //创建数据对象
$result = $Demo->add(); //写入数据库
$this->redirect(‘index’); //成功后重定向到index操作页面
  }
 
//数据查询操作
public function index() {
$Demo = new Model(‘Demo’); //实例化模型类
$list = $Demo->select(); //查询数据
$this->assign(‘list’,$list); //模板变量赋值
$this->display(); //输出模板
}
以上定义后,Index模块就具有了insert和index两个操作,操作方法的定义不需要使用任何参数,而且必须定义为public类型,否则无法访问。
由于只是简单的数据操作应用,所以我们根本不需要创建任何的模型类也同样可以进行CURD操作,这就是新版的魅力所在。^_^

***4.6模板定义
控制器和操作方法已经创建完毕,接下来就是定义模板文件了。
项目的自动生成已经为我们生成了Tpl/default目录,我们只需要在default目录下面创建Index目录,表示存放Index模块的模板文件。由于insert操作是后台操作,并不涉及模板输出,因此不需要定义模板文件,所以我们只要为index操作定义模板即可,内容如下:
<!–数据新增表单–>
 <form method=”post”  action=”__URL__/insert” >
标题:<input type=”text” name=”title”><br />
内容:<textarea name=”content” rows=”5″ cols=”25″></textarea><br/>
<input type=”submit” value=”新增数据”>
 </form>
<!–循环输出查询结果数据集–>
<volist name=’list’ id=’vo’ >
编号:{$vo.id}<br/>
标题: {$vo.title}<br/>
内容: {$vo.content}<hr>
</volist>
把上面的内容保存为Tpl/default/Index/index.html即可。
action=”__URL__/insert”表示提交表单到当前模块的insert操作。

***4.7运行应用
模板定义完成后,我们就可以运行应用了。我们在浏览器里面输入:
http://localhost/Myapp/就可以看到页面的表单输出了。
 
由于我们开启了调试模式,所以在页面的最下面还会看到一些额外的调试信息,并且可以很清楚的看到当前页面的请求信息和执行时间、SQL日志,最后还有加载的文件列表,事实上,页面Trace信息的显示完全是可以定制的,而这些内容不需要在模板里面定义。

在ThinkPHP中,我们称之为页面Trace信息,这是为了在开发过程中调试用的,关闭调试模式后,这些信息会自动消失。另外在调试模式下面,由于开启了日志记录,并且关闭了所有缓存,所以执行效率会有一定影响,但是关闭调试模式后,效率会有非常显著的提高。
可以尝试在页面新增数据,会看到页面下面有列表数据输出。到目前为止,我们已经完成了一个完整的数据操作应用了。

***5开发指南
***5.1配置
***5.1.1配置格式
ThinkPHP框架中所有配置文件的定义格式均采用返回PHP数组的方式,格式为:
<?php return array(
 ‘APP_DEBUG’ => true,
 ‘URL_MODEL’ => 2,
  //更多的配置参数
  // ……
);?>
配置参数不区分大小写(因为无论大小写定义都会转换成小写),所以下面的配置等效:
<?php return array(
 ‘app_debug’ => true,
 ‘url_model’ => 2,
);?>
但是习惯上保持大写定义的原则。
还可以在配置文件中可以使用二维数组来配置更多的信息,例如:
<?php return array(
 ‘APP_DEBUG’ => true,
‘USER_CONFIG’ => array(
             ‘USER_AUTH’ => true,
             ‘USER_TYPE’ => 2,
),
);?>
系统目前最多支持二维数组的配置级别,每个项目配置文件除了定义ThinkPHP所需要的配置参数之外,开发人员可以在里面添加项目需要的一些配置参数,用于自己的应用。项目配置文件的位置默认位于项目的Conf目录。

***5.1.2惯例配置
惯例重于配置是ThinkPHP的一个重要思想,系统内置有一个惯例配置文件(位于Think\Common\convention.php),按照大多数的使用对常用参数进行了默认配置。所以,对于应用项目的配置文件,往往只需要配置和惯例配置不同的或者新增的配置参数,如果你完全采用默认配置,甚至可以不需要定义任何配置文件。
(如果需要了解惯例配置中的详细配置列表请参考附录的配置参考部分。)

***5.1.3项目配置
这里的项目配置指的是项目的全局配置,因为一个项目除了可以定义项目配置文件之外,还可以定义模块配置文件用于针对某个特定的模块进行特殊的配置。他们的定义格式都是一致的,区别只是配置文件命名的不同。系统会自动在不同的阶段读取配置文件。
项目配置文件位于项目的配置文件目录(默认是Conf)下面,文件名是config.php。
在项目配置文件里面除了添加内置的参数配置外,还可以额外添加项目需要的配置参数。
后面的开发指南中提及的配置参数设置如未特别说明,都是指在项目配置文件中定义。

***5.1.4调试配置
如果启用了调试模式的话,那么会导入框架默认的调试配置文件,默认的调试配置文件位于Think\Common\debug.php,如果没有检测到项目的调试配置文件,就会直接使用默认的调试配置参数。项目定义了自身的调试配置文件的话,则会和默认的调试配置文件合并,也就是说,项目配置文件也只需要配置和默认调试配置不同的参数或者新增的参数。
调试配置文件也位于项目配置目录下面,文件名是debug.php。
通常情况下,调试配置文件里面可以进行一些开发模式所需要的配置。例如,配置额外的数据库连接用于调试,开启日志写入便于查找错误信息、开启页面Trace输出更多的调试信息等等。系统默认的调试配置文件中设置了:
 开启日志记录
 关闭模板缓存
 记录SQL日志
 关闭字段缓存
 开启运行时间详细显示(包括内存、缓存情况)
 开启页面Trace信息显示
 严格检查文件大小写(即使是Windows平台)
由于以上的设置涉及到较多的文件IO操作和模板实时编译,所以在开启调试模式的情况下,性能会有一定的下降,不过不用担心,一旦关闭调试模式,性能即可恢复理想的效果。

***5.1.5分组配置
分组配置用于系统启用了分组模式的情况之下,对于每个分组可以单独定义自己的配置文件。
分组配置文件位于:项目配置目录/分组名称/config.php
分组配置的定义格式和项目配置是一样的。分组名称区分大小写。
***5.1.6模块配置
ThinkPHP支持对某些参数进行动态配置,针对这一特性,ThinkPHP还特别引入了模块配置文件的支持,这其实也是动态配置的体现。模块配置文件位于:
项目配置目录/模块名(小写)_config.php //用于不使用分组的情况
或者
项目配置目录/分组名/模块名(小写)_config.php //用于使用分组的情况
模块配置文件的定义格式和项目配置相同。需要注意的是,有些配置参数在读取模块配置之前已经生效,因此可能会发生定义后不起作用的情况。

***5.1.7读取配置
定义了配置文件之后,可以使用系统提供的C方法来读取已有的配置:
C(‘参数名称’) //获取已经设置的参数值
例如,C(‘APP_DEBUG’)可以读取到系统的调试模式的设置值,同样,由于配置参数不区分大小写,因此C(‘app_debug’)是等效的,但是建议使用大写方式的规范。
如果APP_DEBUG尚未存在设置,则返回NULL。
C方法同样可以用于读取二维配置:
C(‘USER_CONFIG.USER_TYPE’) //获取用户配置的用户类型设置
因为配置参数是全局有效的,因此C方法可以在任何地方读取任何配置,哪怕某个设置参数已经生效过期了。后面我们还会了解到C方法同样还具有给配置参数赋值的作用。(如果对C方法的命名比较奇怪的话,可以借助Config单词来帮助记忆)

***5.1.8动态配置
之前的方式都是通过预先定义配置文件的方式,而在具体的Action方法里面,我们仍然可以对某些参数进行动态配置,主要是指那些还没有被使用的参数。
设置新的值:
C(‘参数名称’,’新的参数值’);
例如,我们需要动态改变数据缓存的有效期的话,可以使用
C(‘ DATA_CACHE_TIME’,’60’);
动态改变配置参数的方法和读取配置的方法在使用上面非常接近,都是使用C方法,只是参数的不同。因此掌握C方法的使用对于掌握配置有着关键的作用。
也可以支持二维数组的读取和设置,使用点语法进行操作,如下:
获取已经设置的参数值:
C(‘USER_CONFIG.USER_TYPE’)
设置新的值:
C(‘USER_CONFIG.USER_TYPE’,’1′);

***5.1.9扩展配置
新版的配置文件都具有扩展能力,以往的项目配置文件只有一个配置文件(调试配置和模块配置文件除外),但是新版可以增加任何需要的配置文件定义,在真正执行的过程中会自动汇总到项目配置缓存里面去,而且都可以通过C方法来调用。
通常扩展配置文件的定义是为了某个特殊的需要,而分离出来的配置文件,这样的目的是为了便于维护和便于管理。系统也内置了一些扩展配置文件的定义,其中包括标签库定义,路由定义,静态定义,扩展模块定义,扩展操作定义,标签定义。惯例配置如下:
‘APP_CONFIG_LIST’ => array(‘taglibs’,’routes’,’htmls’,’modules’,’actions’,’tags’),
对于已经定义好的扩展配置文件系统会自动导入,并加入项目配置的缓存文件里面。例如:
路由配置文件routes.php的定义会自动并入:
C(‘_routes_’);
后面怎么用这个扩展配置,就完全看应用自己的需要了,扩展配置对于扩展配置文件的某个配置项的获取,使用下面的方式:
C(‘_扩展配置名称_.configName’);
//例如
C(‘_modules_.extend’);
如果需要增加额外的扩展配置文件,只需要在项目的配置文件里面增加额外的配置文件名称即可,例如:
‘APP_CONFIG_LIST’ => array(‘taglibs’,’routes’,’htmls’,’modules’,’actions’,’tags’,’myconfig’)
 
  注意事项:
 扩展配置文件更改后,需要删除项目编译缓存文件才会生效;
 对于没有定义的扩展配置文件系统不会自动加载;
 注意扩展配置文件里面的配置参数的获取方式有别于一般的项目配置参数。
考虑到扩展配置的特殊需要,扩展配置里面的设置项是有大小写区分的。

***5.2控制器

***5.2.1模块和操作
ThinkPHP采用模块和操作的方式来执行,首先,用户的请求会通过入口文件生成一个应用实例,应用控制器(我们称之为核心控制器)会管理整个用户执行的过程,并负责模块的调度和操作的执行,并且在最后销毁该应用实例。任何一个WEB行为都可以认为是一个模块的某个操作,系统会根据当前的URL来分析要执行的模块和操作。这个分析工作由URL调度器来实现,官方内置了Dispatcher类来完成该调度。
在Dispatcher调度器中,会根据
http://servername/appName/moduleName/actionName/params
来获取当前需要执行的项目(appName)、模块(moduleName)和操作(actionName),在某些情况下,appName可以不需要(通常是网站的首页,因为项目名称可以在入口文件中指定,这种情况下,appName就会被入口文件替代)。在复杂一点的情况下面,可能还会出现分组(groupName)。
每个模块是一个Action文件,类似于我们平常所说的控制器,系统会自动寻找项目类库Action目录下面的相关类,如果没有找到,则会定位到空模块,否则抛出异常。
而actionName操作是首先判断是否存在Action类的公共方法,如果不存在则会继续寻找父类中的方法,如果依然不存在,就会寻找是否存在自动匹配的模版文件。如果存在模版文件,那么就直接渲染模版输出。
因此应用开发中的一个重要过程就是给不同的模块定义具体的操作。一个应用如果不需要和数据库交互的时候可以不需要定义模型类,但是必须定义Action控制器。
Action控制器的定义非常简单,只要继承Action基础类就可以了,例如:
Class UserAction extends Action{
}
如果我们要执行下面的URL
http://servername/index.php/User/add
你需要增加一个add方法就可以了,例如
Class UserAction extends Action{
  // 定义一个add操作方法,注意操作方法不需要任何参数
  Public function add(){
   // add操作方法的逻辑实现
   // ……
   $this->display(); // 输出模板页面
}
}
操作方法必须定义为Public类型,否则会报错。并注意操作方法的命名不要和内置的Action类的方法重复。系统会自动定位当前操作的模板文件,而默认的模板文件应该位于项目目录下面的Tpl\default\User\add.html。

***5.2.2默认模块和操作
如果使用http://<serverName>/index.php,没有带任何模块和操作的参数,系统就会寻找默认模块和默认操作,通过DEFAULT_MODULE和DEFAULT_ACTION来定义,系统的默认模块设置是Index模块,默认操作设置是index操作。也就是说:
http://<serverName>/index.php
http://<serverName>/index.php/Index以及
http://<serverName>/index.php/Index/index等效。
可以在项目配置文件中修改默认模块和默认操作的名称。

***5.2.3模块分组
模块分组功能是为了更好的组织已有的模块,并且增加项目容量的一个有效机制。分组功能可以把以往的多项目合并到一个项目中去,这样一来,之前需要采用跨项目操作的地方,现在因为在一个项目中从而免去了不少麻烦,并且公共文件的重用也方便了,并且每个分组都可以有自己独立的配置文件、公共文件、语言包,在URL的访问上面也非常清晰。
要启用分组模块非常简单,配置下APP_GROUP_LIST参数和DEFAULT_GROUP参数即可。
例如我们把当前的项目分成Home和Admin两个组,分别表示前台和后台功能,那么只需要进行下面的配置:
  ‘APP_GROUP_LIST’=>’Admin,Home’,
  ‘DEFAULT_GROUP’=>’Home’,
需要注意的是,一定要把上面的配置参数放入项目的配置文件,而不是项目的分组配置或者模块配置文件。多个分组之间用逗号分隔即可,默认分组只允许设置一个。
在我们启用项目分组之前,由于使用的两个项目,所以URL地址分别是:
http://<serverName>/index.php/Index/index  Home项目地址
http://<serverName>/Admin/index.php/Index/index  Admin项目地址
采用了分组模式后,URL地址变成:
http://<serverName>/index.php/Home/Index/index
如果Home是默认分组的话 还可以变成 http://<serverName>/index.php/Index/index
http://<serverName>/index.php/Admin/Index/index
如果设置了隐藏index.php的话,两者的URL表现效果基本上是一致的,但是从管理和公共调用的角度来看,确实方便了不少。当使用分组模式时,目录结构只是做了一点小小的扩展,主要区别在于项目类库目录和模板目录下面多了一层分组目录。
如果不使用分组模式的话,Action目录下面应该是所有的Action类库,现在我们可以在Action目录下面创建自己的分组目录,例如我们把当前项目分成了Home和Admin两个组,那么就需要在Action目录下面创建Home和Admin目录,然后把属于各自的Action类库放到对应的目录下面。如果某个Action类库是每个分组都需要使用或者公共继承的话,可以把这个公共Action类库放到分组目录之外,并且利用ThinkPHP的自动加载机制无需手动引入。
使用了模块分组后,如果需要实例化其他分组的模块类,可以使用:
A(‘Home.User’);// 实例化Home分组的UserAction类
对于分组模式下面的Model类库是否需要分组完全看项目的需要,由于通常不同的分组对应的数据表是相同的,因此,我们推荐Model类库不分组存放,仍然保留之前的方式,无论是什么分组都公共调用Model类库。如果确实需要分组的话,仍然可以按照Action的方式,在Model目录下面创建Home和Admin目录,然后放入对应的Model类库,采用这种方式的话,模型类的调用方法有所区别。
如果模型类也分组存放,在使用D方法调用的时候需要使用:
$User = D(‘Home.User’);// 实例化Home分组下面的UserModel类
模板文件的分组和Action类库分组也基本类似,在原来的模板主题目录下面增加一个分组目录即可。
例如:
Tpl/default/Home/Index/index.html
Tpl/default/Admin/User/index.html
相比之前的模板文件位置就是多了一个分组目录Home和Admin,如果觉得目录结构太深了,可以配置 TMPL_FILE_DEPR参数 来减少目录层次,该参数默认是 “/”,如果改成
 ‘TMPL_FILE_DEPR’=>’_’
那么分组的模板文件就变成了
Tpl/default/Home/Index_index.html
Tpl/default/Admin/User_index.html
分组模块的概念,并不局限于将项目区分为前台和后台。你可以按自己所需类型,进行明确细致的区分,这样非常方便于项目管理和开发部署。
分组模块下面的具体模块和之前的模块功能没有任何区别,已有的URL和模块功能都可以很好的支持,例如空模块、空操作、伪静态等等。
更多的关于分组模式下面URL方面的区别可以查看URL生成部分的U方法的使用。

***5.2.4URL模式
我们在上面的执行过程里面看到的URL是默认情况下,其实ThinkPHP支持四种URL模式,可以通过设置URL_MODEL参数来定义,包括普通模式、PATHINFO、REWRITE和兼容模式。
一、普通模式 :设置URL_MODEL为0
采用传统的URL参数模式
http://<serverName>/appName/?m=module&a=action&id=1
普通URL模式和在关闭URL_DISPATCH_ON的情况下面的效果是一样的,只是普通URL模式还具有路由功能。如果你并不需要使用路由功能,而且还在使用普通模式的话,建议直接关闭URL_DISPATCH_ON,效率会更高。
二、PATHINFO模式 :设置URL_MODEL为1
默认情况使用PATHINFO模式,ThinkPHP内置强大的PATHINFO支持,提供灵活和友好URL支持。PATHINFO模式根据不同的设置还包括普通模式和智能模式两种:
普通模式 设置URL_PATHINFO_MODEL参数为1
该模式下面URL参数没有顺序,例如
http://<serverName>/appName/m/module/a/action/id/1
http://<serverName>/appName/a/action/id/1/m/module
以上URL等效
智能模式 设置URL_PATHINFO_MODEL参数为2(系统默认的模式)
自动识别模块和操作,例如
http://<serverName>/appName/module/action/id/1/ 或者
http://<serverName>/appName/module,action,id,1/
在智能模式下面,第一个参数会被解析成模块名称(或者路由名称,下面会有描述),第二个参数会被解析成操作(在第一个参数不是路由名称的前提下),后面的参数是显式传递的,而且必须成对出现,例如:
http://<serverName>/appName/module/action/year/2008/month/09/day/21/
其中参数之间的分割符号由URL_PATHINFO_DEPR参数设置,默认为”/”,例如我们设置URL_PATHINFO_DEPR为“-”的话,就可以使用下面的URL访问
http://<serverName>/appName/module-action-id-1/
注意不要使用”:”和”&”符号进行分割,该符号有特殊用途。
略加修改,就可以展示出富有诗意的URL,呵呵~
如果想要简化URL的形式可以通过路由功能(后面会有描述)以及空模块和空操作。
在PATH_INFO模式下面,会把相关参数转换成GET变量,以及并入REQUEST变量,因此不妨碍URL里面的GET和REQUEST变量获取。
三、REWRITE模式: 设置URL_MODEL为2
该URL模式和PATHINFO模式功能一样,除了可以不需要在URL里面写入口文件,和可以定义.htaccess文件外。在开启了Apache的URL_REWRITE模块后,就可以启用REWRITE模式了,具体参考下面的URL重写部分。
四、兼容模式: 设置URL_MODEL为3
兼容模式是普通模式和PATHINFO模式的结合,并且可以让应用在需要的时候直接切换到PATHINFO模式而不需要更改模板和程序。兼容模式URL可以支持任何的运行环境。
兼容模式的效果是:
http://<serverName>/appName/?s=/module/action/id/1/
并且也可以支持参数分割符号的定义,例如在URL_PATHINFO_DEPR为~的情况下,下面的URL有效:
http://<serverName>/appName/?s=module~action~id~1
其实是利用了VAR_PATHINFO参数,用普通模式的实现模拟了PATHINFO的模式。但是兼容模式并不需要自己传s变量,而是由系统自动完成URL部分。正是由于这个特性,兼容模式可以和PATHINFO模式之间直接切换,而不需更改模板文件里面的URL地址连接。
某些服务器环境不能良好的支持PATHINFO,或者需要进行额外的配置才可以支持,如果你确认你的服务器环境不支持PATHINFO,可以选择普通模式或者兼容模式URL运行。

***5.2.5URL路由
ThinkPHP支持URL路由功能,要启用路由功能,需要设置URL_ROUTER_ON参数为true。开启路由功能后,系统会自动进行路由检测,如果在路由定义里面找到和当前URL匹配的路由名称,就会进行路由解析和重定向。路由功能需要定义路由定义文件,位于项目的配置目录下面,文件名为routes.php,定义格式:
return array(
//第一种方式 常规路由
‘RouteName’=>array(‘模块名称’, ‘操作名称’, ‘参数定义’, ‘额外参数’),
//第二种方式 泛路由
‘RouteName@’=>array(
array(‘路由匹配正则’, ‘模块名称’, ‘操作名称’, ‘参数定义’, ‘额外参数’),
),
…更多的路由名称定义
)
系统在执行Dispatch解析的时候,会判断当前URL是否存在定义的路由名称,如果有就会按照定义的路由规则来进行URL解析。例如,我们启用了路由功能,并且定义了下面的一个路由规则: 
‘blog’=>array(‘Blog’, ‘archive’, ‘year,month,day’, ‘userId=1&status=1′)
那么我们在执行
http://<serverName>/appName/blog/2009/10/1/的时候就会实际执行Blog模块的archive操作,后面的参数/2009/10/1/就会依次按照 year/month/day来解析,并且会隐含传入userId=1和status=1两个参数。
另外一种路由参数的传入是
http://<serverName>/appName/?r=blog&year=2009&month=10&day=1,会执行上面相同的路由解析,该方式主要是提供不支持PATHINFO方式下面的兼容实现。其中r由VAR_ROUTER参数定义,默认是r。
如果需要路由到分组模块的话,可以定义成
‘blog’=>array(‘Home.Blog’, ‘archive’, ‘year,month,day’, ‘userId=1&status=1′)
就可以指定路由到Home分组的Blog模块。
 
泛路由支持
泛路由指的是对同一个路由名称提供了多个规则的支持,使得URL的设置更加灵活,例如,我们对Blog路由名称需要有多个规则的路由:
         ‘Blog@’=>array(
                   array(‘/^\/(\d+)(\/p\/\d)?$/’,’Blog’,’read’,’id’),
                   array(‘/^\/(\d+)\/(\d+)/’,’Blog’,’archive’,’year,month’),
                  ),
第一个路由规则表示解析 Blog/123这样的URL到Blog模块的read操作
第二个路由规则表示解析 Blog/2009/10这样的URL到Blog模块的archive操作
泛路由的定义难度就在路由正则的定义上面,其它参数和常规路由的使用一致。
 
举个简单路由的例子,如果我们有一个City模块,而我们希望能够通过类似下面这样的URL地址来访问具体某个城市的操作:
http://<serverName>/index.php/City/shanghai/
shanghai这个操作方法是不存在的,我们给相关的城市操作定义了一个city方法,如下:
Class CityAction extends Action{
public function city(){
// 读取城市名称
$cityName = $_GET[‘name’];
Echo (‘当前城市: ‘.$cityName);
}
}
接下来我们来定义路由文件,实现类似于
http://<serverName>/index.php/City/shanghai/
这样的解析,路由文件名称是
return array(
‘City’=>array(‘City’, ‘city’, ‘name’);
);
这样,URL里面所有的City模块都会被路由到City模块的city操作,而后面的第二个参数会被解析成 $_GET[‘name’]
接下来,我们就可以在浏览器里面输入
http://<serverName>/index.php/City/beijing/
http://<serverName>/index.php/City/shanghai/
http://<serverName>/index.php/City/shenzhen/
会看到依次输出的结果是:
当前城市:beijing
当前城市:shanghai
当前城市:shenzhen

***5.2.6URL伪静态
系统支持伪静态URL设置,可以通过设置URL_HTML_SUFFIX参数随意在URL的最后增加你想要的静态后缀,而不会影响当前操作的正常执行。例如,我们设置URL_HTML_SUFFIX为 .shtml的话,我们可以把下面的URL
http://<serverName>/Blog/read/id/1
变成
http://<serverName>/Blog/read/id/1.shtml
后者更具有静态页面的URL特征,但是具有和前面的URL相同的执行效果,并且不会影响原来参数的使用。注意配置设置时要包含后缀中的“.”。
伪静态设置后,如果需要动态生成一致的URL,可以使用U方法在模板文件里面生成URL。
关于U方法的使用请参考后面的URL生成部分。

***5.2.7URL重写
通常的URL里面含有index.php,为了达到更好的SEO效果可能需要去掉URL里面的index.php,通过URL重写的方式可以达到这种效果,通常需要服务器开启URL_REWRITE模块才能支持。
下面是Apache的配置过程,可以参考下:
1、httpd.conf配置文件中加载了mod_rewrite.so模块
2、AllowOverride None将None改为 All
3、确保URL_MODEL设置为2
4、把.htaccess文件放到入口文件的同级目录下
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
</IfModule>

***5.2.8URL生成
为了配合所使用的URL模式,我们需要能够动态的根据当前的URL设置生成对应的URL地址,为此,ThinkPHP内置提供了U方法,用于URL的动态生成,可以确保项目在移植过程中不受环境的影响。
U方法的定义规则如下(方括号内参数根据实际应用决定):
U('[项目://][路由@][分组名-模块/]操作?参数1=值1[&参数N=值N]’)
或者用数组的方式传入参数
U('[项目://][路由@][分组名-模块/]操作’,array(‘参数1’=>’值1′ [,’参数N’=>’值N’]))
如果不定义项目和模块的话 就表示当前项目和模块名称,下面是一些简单的例子:
U(’Myapp://User/add’) //生成Myapp项目的User模块的add操作的URL地址
U(’Blog/read?id=1’) //生成Blog模块的read操作 并且id为1的URL地址
U(’Admin-User/select’) //生成Admin分组的User模块的select操作的URL地址
参数请确保使用 ?id=1&name=tp或者数组的方式来定义,虽然有些情况下
U(’Blog/read/id/1’)和U(’Blog/read?id=1’)的效果一样,但是在不同的URL设置情况下,会导致解析的错误。
根据项目的不同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/Blog/read/id/1
如果当前URL设置为REWRITE模式的话,同样的方法最后生成的URL地址是:
http://<serverName>/Blog/read/id/1
如果当前URL设置为REWRITE模式,并且设置了伪静态后缀为.html的话,同样的方法最后生成的URL地址是:
http://<serverName>/Blog/read/id/1.html
U方法还可以支持路由,如果我们定义了一个名称为View的路由,指向Blog模块的read操作,参数是id,那么U(‘View@?id=1′)生成的URL地址是:
http://<serverName>/index.php/View/id/1

***5.2.9URL大小写
我们知道,系统默认的规范是根据URL里面的moduleName和actionName来定位到具体的模块类,从而执行模块类的操作方法,如果在Linux环境下面,就会发生URL里面使用小写模块名不能找到模块类的情况,例如在Linux环境下面,我们访问下面的URL是正常的:
http://<serverName>/index.php/User/add
但是,如果使用
http://<serverName>/index.php/user/add
就会出现user模块不存在的错误。因为,我们定义的模块类是UserAction而不是userAction,但是后者显然不符合ThinkPHP的命名规范,显然这样的问题会造成用户体验的下降。
其实,系统本身已经提供了一个很好的解决方案,可以通过配置简单实现。
只要在项目配置中,增加:
‘URL_CASE_INSENSITIVE’ =>   true
就可以实现URL访问不再区分大小写了。
http://<serverName>/index.php/User/add
将等效于
http://<serverName>/index.php/user/add
这里需要注意一个地方,如果我们定义了一个UserTypeAction的模块类,那么URL的访问应该是:
http://<serverName>/index.php/user_type/list
而不是
http://<serverName>/index.php/usertype/list
如果设置
‘URL_CASE_INSENSITIVE’ =>   false
的话,URL就又变成:
http://<serverName>/index.php/UserType/list

***5.2.10空操作
空操作是指系统在找不到指定的操作方法的时候,会定位到空操作(_empty)方法来执行,利用这个机制,我们可以实现错误页面和一些URL的优化。
例如,我们前面用URL路由实现了一个城市切换的功能,下面我们用空操作功能来重新实现。
我们只需要给CityAction类定义一个_emtpy(空操作)方法:
Class CityAction extends Action{
Public function _empty(){
// 把所有城市的操作都解析到city方法
$cityName = ACTION_NAME;
$this->city($cityName);
}
// 注意city方法本身是protected方法
Protected function city($name){
// 和$name 这个城市相关的处理
Echo (‘当前城市: ‘.$name);
}
}
接下来,我们就可以在浏览器里面输入
http://<serverName>/index.php/City/beijing/
http://<serverName>/index.php/City/shanghai/
http://<serverName>/index.php/City/shenzhen/
会看到依次输出的结果是:
当前城市:beijing
当前城市:shanghai
当前城市:shenzhen
可以看出来,和用URL路由实现的效果是一样的,而且不需要定义路由定义文件。

***5.2.11空模块
空模块的概念是指当系统找不到指定的模块名称的时候,系统会尝试定位空模块(EmptyAction),利用这个机制我们可以用来定制错误页面和进行URL的优化。
现在我们把前面的需求进一步,把URL由原来的
http://<serverName>/index.php/City/shanghai/
变成
http://<serverName>/index.php/shanghai/
这样更加简单的方式,如果按照传统的模式,我们必须给每个城市定义一个Action类,然后在每个Action类的index方法里面进行处理。 可是如果使用空模块功能,这个问题就可以迎刃而解了。 我们可以给项目定义一个EmptyAction类
Class EmptyAction extends Action{
Public function index(){
// 根据当前模块名称来判断要执行哪个城市的操作
$cityName = MODULE_NAME;
$this->city($cityName);
}
Protected function city($name){
// 和$name 这个城市相关的处理
Echo (‘当前城市: ‘.$name);
}
}
接下来,我们就可以在浏览器里面输入
http://<serverName>/index.php/beijing/
http://<serverName>/index.php/shanghai/
http://<serverName>/index.php/shenzhen/
会看到依次输出的结果是:
当前城市:beijing
当前城市:shanghai
当前城市:shenzhen

***
5.2.12前置和后置操作
系统会检测当前操作是否具有前置和后置操作,如果存在就会按照顺序执行,例如,我们在UserAction类里面定义了_before_insert()和 _after_insert()操作,那么执行User模块的insert操作的时候,会按照顺序执行下面的操作:
_before_insert
insert
_after_insert
特殊情况是,当前的add操作并没有定义操作方法,而是直接渲染模板文件,那么如果定义了_before_add和 _after_add方法的话,依然会生效,也会按照这个顺序来执行add操作。真正有模板输出的可能仅仅是当前的add操作,前置和后置操作一般情况是没有任何输出的。前置和后置操作的方法名是在要执行的方法前面加 _before_和_after_,例如:
Class CityAction extends Action{
public function _before_index() {  
    echo ‘before';
}  
public function index(){  
    echo ‘index';  
}  
public function _after_index() {  
    echo ‘after';  
}  
}
执行结果会先输出before然后输出index最后输出after。对于任何操作方法我们都可以按照这样的规则来定义前置和后置方法。
需要注意的是,在有些方法里面使用了exit或者错误输出之类的话 有可能不会再执行after后置方法了。

***5.2.13操作链
ThinkPHP支持使用操作链的方式,例如,我们访问下面的URL:
http://serverName/appName/User/action1:action2:action3/
那么会依次执行UserAction的action1 action2 action3方法,并且当前操作名称是最后一个操作。在进行默认模板输出的时候会用到。如果确实需要在不同的操作方法中都进行输出,请确保在Action的display方法中指定需要渲染的模板文件名。否则,只能输出最后的操作模板。使用了操作链后,前置和后置方法会失效。

***5.2.14跨模块调用
在开发过程中经常会在当前模块调用其他模块的方法,这个时候就涉及到跨模块调用,我们还可以了解到A和R两个快捷方法的使用。
$User = A(“User”); // 实例化UserAction控制器对象
$User->importUser(); // 调用User模块的importUser操作方法
这里的A(“User”)是一个快捷方法,和下面的代码等效:
import(“@.Action.UserAction”);
$User = newUserAction();
事实上,在这个例子里面还有比A方法更简单的调用方法,例如:
 R(“User”,”importUser”); // 远程调用UserAction控制器的importUser操作方法
 
上面只是在当前项目中调用,如果你有需要在多个项目之间调用方法,一样可以完成:
$User = A(“User”,”App2″); // 实例化App2项目的UserAction控制器对象
$User->importUser();
 // 远程调用App2项目的UserAction控制器的importUser操作方法
 R(“User”,”importUser”,”App2″);

***5.2.15页面跳转
在应用开发中,经常会遇到一些带有提示信息的跳转页面,例如操作成功或者操作错误页面,并且自动跳转到另外一个目标页面。系统的Action类内置了两个跳转方法success和error,用于页面跳转提示,而且可以支持ajax提交。使用方法很简单,举例如下:
$User = M(“User”); // 实例化User对象
$result = $User->add($data);
if ($result){
// 设置成功后的跳转页面地址 默认的返回页面是$_SERVER[“HTTP_REFERER”]
$this->assign(“jumpUrl”,”/User/list/”);
       $this->success(“新增成功!”);
}else{
// 错误页面的默认跳转页面是返回上一页 通常可以不用设置
       $this->error(“新增错误!”);
}
Success和error方法都有对应的模板,并且是可以设置的,默认的设置Public:success和Public:error,模板文件可以使用模板标签,并且可以使用下面的模板变量:
$msgTitle:操作标题
$message:页面提示信息
$status:操作状态  1表示成功 0表示失败 具体还可以由项目本身定义规则
$waitSecond:跳转等待时间 单位为妙
$jumpUrl:跳转页面地址
如果是AJAX方式提交的话,success和error方法会调用ajaxReturn方法返回信息,具体可以参考后面的AJAX返回部分。

***5.2.16重定向
Action类的redirect方法可以实现页面的重定向功能。
redirect方法的参数用法和U函数的用法一致(参考上面的URL生成部分),例如:
$this->redirect(‘User/list’, array(‘cate_id’=>2), 5,’页面跳转中~’)
上面的用法是停留5秒后跳转到User模块的list操作,并且显示页面跳转中字样,重定向后会改变当前的URL地址。

***5.2.17AJAX返回
系统支持任何的AJAX类库,提供了ajaxReturn方法用于AJAX调用后返回数据给客户端。
并且支持JSON、XML和EVAL三种方式给客户端接受数据,通过配置DEFAULT_AJAX_RETURN进行设置,在选择不同的AJAX类库的时候可以使用不同的方式返回数据。
要使用ThinkPHP的ajaxReturn方法返回数据的话,需要遵守一定的返回数据的格式规范。ThinkPHP返回的数据格式包括:
status操作状态
info提示信息
data返回数据
返回数据data可以支持字符串、数字和数组、对象,返回客户端的时候根据不同的返回格式进行编码后传输。如果是JSON格式,会自动编码成JSON字符串,如果是XML方式,会自动编码成XML字符串,如果是EVAL方式的话,只会输出字符串data数据,并且忽略status和info信息。
下面是一个简单的例子:
$User = M(“User”); // 实例化User对象
$result = $User->add($data);
if ($result){
// 成功后返回客户端新增的用户ID,并返回提示信息和操作状态
       $this->ajaxReturn($result,”新增成功!”,1);
}else{
// 错误后返回错误的操作状态和提示信息
       $this->ajaxReturn(0,”新增错误!”,0);
}
注意,确保你是使用AJAX提交才使用ajaxReturn方法。
在客户端接受数据的时候,根据使用的编码格式进行解析即可。

***5.3模型
***5.3.1定义和实例化
在ThinkPHP2.0版本中,可以无需进行任何模型定义。只有在需要封装单独的业务逻辑的时候,模型类才是必须被定义的,因此ThinkPHP在模型上有很多的灵活和方便性,让你无需因为表太多而烦恼。
根据不同的模型定义,我们有几种实例化模型的方法,下面来分析下什么情况下用什么方法:
1、实例化基础模型(Model) 类
在没有定义任何模型的时候,我们可以使用下面的方法实例化一个模型类来进行操作:
$User = new Model(‘User’);
或者使用M快捷方法实例化是等效的
$User = M(‘User’);
$User->select(); //进行其他的数据操作
这种方法最简单高效,因为不需要定义任何的模型类,所以支持跨项目调用。缺点也是因为没有自定义的模型类,因此无法写入相关的业务逻辑,只能完成基本的CURD操作。
 
2、实例化其他模型类
第一种方式实例化因为没有模型类的定义,因此很难封装一些额外的逻辑方法,不过大多数情况下,也许只是需要扩展一些通用的逻辑,那么就可以尝试下面一种方法。
M方法默认是实例化Model类,如果需要实例化其他模型类,可以使用
$User = M(‘User’, ‘CommonModel’);
上面的方法等效于
$User = new CommonModel(‘User’);
因为系统的模型类都能够自动加载,因此我们不需要在实例化之前手动进行类库导入操作。模型类commonModel必须继承Model,如果没有定义别名导入的话,需要放在项目Model下。我们可以在CommonModel类里面定义一些通用的逻辑方法,就可以省去为每个数据表定义具体的模型类,如果你的项目已经有超过100个数据表了,而大多数情况都是一些基本的CURD操作的话,只是个别模型有一些复杂的业务逻辑需要封装,那么第一种方式和第二种方式的结合是一个不错的选择。
 
3、实例化用户定义的模型(×××Model)类
这种情况是使用的最多的,一个项目不可避免的需要定义自身的业务逻辑实现,就需要针对每个数据表定义一个模型类,例如UserModel、InfoModel等等。
定义的模型类通常都是放到项目的Lib\Model目录下面。例如,
class UserModel extends Model{
        Public function myfun(){
     //添加自己的业务逻辑
     // ………
}
}
其实模型类还可以继承一个用户自定义的公共模型类,而不是只能继承Model类。
要实例化自定义模型类,可以使用下面的方式:
$User = new UserModel();
或者使用D快捷方法实例化是等效的
$User = D(‘User’);
$User->select(); //进行其他的数据操作
D方法可以自动检测模型类,不存在时系统会抛出异常,同时对于已实例化过的模型,不会重复去实例化。默认的D方法只能支持调用当前项目的模型,如果需要跨项目调用,需要使用:
$User = D(‘User’, ‘Admin’); // 实例化Admin项目下面的User模型
$User->select();
如果启用了模块分组功能,可使用:
$User = D(‘Admin.User’);
 
4、实例化空模型类
如果你仅仅是使用原生SQL查询的话,不需要使用额外的模型类,实例化一个空模型类即可进行操作了,例如:
$Model = new Model();
//或者使用M快捷方法实例化是等效的
// $Model = M();
$Model->query(‘SELECT * FROM think_user where status=1′);
空模型类也支持跨项目调用。
在后面的内容中,针对M方法或者D方法将不再具体说明,请自行分析。

***5.3.2模型命名
当我们创建一个UserModel类的时候,其实已经遵循了系统的约定。ThinkPHP要求数据库的表名和模型类的命名遵循一定的规范,首先数据库的表名和字段全部采用小写形式,模型类的命名规则是除去表前缀的数据表名称,并且首字母大写,然后加上模型类的后缀定义,例如:
UserModel表示User数据对象,(假设数据库的前缀定义是 think_)其对应的数据表应该是
think_user
UserTypeModel对应的数据表是 think_user_type
如果你的规则和系统的约定不符合,那么需要设置Model类的tableName属性。
在ThinkPHP的模型里面,有两个数据表名称的定义:
1、tableName不包含表前后缀的数据表名称,一般情况下默认和模型名称相同,只有当你的表名和当前的模型类的名称不同的时候才需要定义。
2、trueTableName包含前后缀的数据表名称,也就是数据库中的实际表名,该名称无需设置,只有当上面的规则都不适用的情况或者特殊情况下才需要设置。
下面举个例子来加深理解:
例如,在数据库里面有一个think_categories表,而我们定义的模型类名称是CategoryModel,按照系统的约定,这个模型的名称是Category,对应的数据表名称应该是think_category(全部小写),但是现在的数据表名称是think_categories,因此我们就需要设置tableName属性来改变默认的规则(假设我们已经在配置文件里面定义了DB_PREFIX为 think_)。
protected $tableName = ‘categories';
注意这个属性的定义不需要加表的前缀think_
而对于另外一种特殊情况,数据库中有一个表(top_depts)的前缀和其它表前缀不同,不是think_而是 top_,这个时候我们就需要定义 trueTableName属性了
protected $trueTableName = ‘top_depts';
注意trueTableName需要完整的表名定义
除了数据表的定义外,还可以对数据库进行定义:
dbName定义模型当前对应的数据库名称,只有当你当前的模型类对应的数据库名称和配置文件不同的时候才需要定义,例如:
protected $dbName = ‘top';
另外,我们来了解下表后缀的含义。表后缀通常情况下用处不大,因为这个和表的设计有关。但是个别情况下也是有用,例如,我们在定义数据表的时候统一采用复数形式定义,下面是我们设计的几个表名 think_users、think_categories、think_blogs,我们定义的模型类分别是UserModel、CategoryModel、BlogModel,按照上面的方式,我们必须给每个模型类定义tableName属性。其实我们可以通过设置表后缀的方式来实现相同的效果,我们可以设置DB_SUFFIX配置参数为s,那么系统在获取真实的表名的时候就会自动加上这个定义的表后缀,我们就不必给每个模型类定义tableName属性了,而只是对categories这样的复数情况单独定义trueTableName属性就可以了。

***5.3.3获取字段
我们在UserModel类里面根本没有定义任何User表的字段信息,但是系统是如何做到属性对应数据表的字段呢?这是因为ThinkPHP可以在运行时自动获取数据表的字段信息(确切的说,是在第一次运行的时候,而且只需要一次,以后会永久缓存字段信息,除非设置不缓存或者删除),包括数据表的主键字段和是否自动增长等等,如果需要显式获取当前数据表的字段信息,可以使用模型类的getDbFields方法来获取。如果你在开发过程中修改了数据表的字段信息,可能需要清空Data/_fields目录下面的缓存文件,让系统重新获取更新的数据表字段信息。
如果你没有定义模型类,进行相关操作的时候一样会生成字段缓存文件。
也可以在模型类里面手动定义数据表字段的名称,可以避免IO加载的效率开销,在模型类里面添加fields属性即可,定义格式如下:
class UserModel extends Model{
protected $fields = array(
‘id’,
‘username’,
’email’,
‘age’,
‘_pk’=>’id’,
‘_autoinc’=>true
)
}
其中_pk表示主键字段名称 _autoinc表示主键是否自动增长类型
可以通过设置DB_FIELDS_CACHE参数来关闭字段自动缓存,如果在开发的时候经常变动数据库的结构,而不希望进行数据表的字段缓存,可以在项目配置文件中增加如下配置:
‘DB_FIELDS_CACHE’=>false
调试模式下面由于考虑到数据结构可能会经常变动,所以默认是关闭字段缓存的。ThinkPHP的默认约定每个数据表的主键名采用统一的id作为标识,并且是自动增长类型的。系统会自动识别当前操作的数据表的字段信息和主键名称,所以即使你的主键不是id,也无需进行额外的设置,系统会自动识别。要在外部获取当前数据对象的主键名称,请使用下面的方法:
$pk = $Model->getPk();
目前不支持联合主键的自动操作。
在个别情况下,可能不需要对当前操作的数据表进行字段缓存,或许是由于采用了动态方式或者当前模型根本没有任何相关的数据表,我们可以设置autoCheckFields属性来关闭某个模型类的字段获取和缓存。
使用getDbFields方法可以获取当前数据对象的全部字段信息:
$fields = $User->getDbFields();

***5.3.4属性访问
因为Model对象本身也是一个数据对象,所以属性的访问就显得非常直观和简单。
ThinkPHP利用了PHP5的魔术方法机制来实现了属性的直观访问。这也是最常用的访问方式,通过数据对象访问,例如
$User = new Model(‘User’);
$User->find(1);
//获取name属性的值
echo $User->name;
//设置name属性的值
$User->name = ‘ThinkPHP’
还有一种属性的操作方式是通过返回数组的方式:
$User = D(“User”);
//注意这里返回的user数据是一个数组
$user = $User->find(1);
//获取name属性的值
echo $user[‘name’];
//设置name属性的值
$user[‘name’] = ‘ThinkPHP’;
两种方式的属性区别是一个是对象的属性,一个是数组的索引名称。

***5.3.5跨库操作
ThinkPHP可以支持模型的同一数据库服务器的跨库操作,跨库操作只需要简单配置一个模型所在的数据库名称即可,例如,假设UserModel对应的数据表在数据库user下面,而InfoModel对应的数据表在数据库info下面,那么我们只需要进行下面的设置即可。
class UserModel extends Model {
protected $dbName = ‘user';
}
class InfoModel extends Model {
protected $dbName = ‘info';
}
在进行查询的时候,系统能够自动添加当前模型所在的数据库名。
$User = D(‘User’);
$User->select();
echo $User->getLastSql();
// 输出的SQL语句为 select * from user.think_user
模型的表前缀取的是项目配置文件定义的数据表前缀,如果跨库操作的时候表前缀不是统一的,那么我们可以在模型里面单独定义表前缀,例如:
protected $tablePrefix = ‘other_';

***5.3.6连接数据库
ThinkPHP内置了抽象数据库访问层,把不同的数据库操作封装起来,我们只需要使用公共的Db类进行操作,而无需针对不同的数据库写不同的代码和底层实现,Db类会自动调用相应的数据库适配器来处理。目前的数据库包括Mysql、MsSQL、PgSQL、Sqlite、Oracle、Ibase以及PDO的支持,如果应用需要使用数据库,必须配置数据库连接信息,数据库的配置文件有多种定义方式:
第一种 在项目配置文件里面定义
return array(
‘DB_TYPE’=> ‘mysql’,
‘DB_HOST’=> ‘localhost’,
‘DB_NAME’=>’thinkphp’,
‘DB_USER’=>’root’,
‘DB_PWD’=>”,
‘DB_PORT’=>’3306′,
‘DB_PREFIX’=>’think_’,
// 其他项目配置参数………
);
 
系统推荐使用该种方式,因为一般一个项目的数据库访问配置是相同的。该方法系统在连接数据库的时候会自动获取,无需手动连接。
可以对每个项目定义不同的数据库连接信息,还可以在调试配置文件里面定义调试数据库的配置信息,如果在项目配置文件和调试模式配置文件里面同时定义了数据库连接信息,那么在调试模式下面后者生效,部署模式下面前者生效。
 
第二种 使用DSN方式在初始化Db类的时候传参数
$db_dsn = “mysql://username:passwd@localhost:3306/DbName”;
$db = new Db($db_dsn);
该方式主要用于在控制器里面自己手动连接数据库的情况,或者用于创建多个数据库连接。
 
第三种 使用数组传参数
$DSN = array(
‘dbms’     => ‘mysql’, 
‘username’ => ‘username’, 
‘password’ => ‘password’, 
‘hostname’ => ‘localhost’, 
‘hostport’ => ‘3306’, 
‘database’ => ‘dbname’
);
 $db = new Db($DSN);
该方式也是用于手动连接数据库的情况,或者用于创建多个数据库连接。
 
第四种 在模型类里面定义
protected $connection = array(
‘dbms’     => ‘mysql’, 
‘username’ => ‘username’, 
‘password’ => ‘password’, 
‘hostname’ => ‘localhost’, 
‘hostport’ => ‘3306’, 
‘database’ => ‘dbname’
);
//或者使用下面的定义
protected $connection = ”mysql://username:passwd@localhost:3306/DbName”;
如果在某个模型类里面定义了connection属性,则在实例化模型对象的时候,会使用该数据库连接信息进行数据库连接。通常用于某些数据表位于当前数据库连接之外的其它数据库。
ThinkPHP并不是在一开始就会连接数据库,而是在有数据查询操作的时候才会去连接数据库。额外的情况是,在系统第一次操作模型的时候,框架会自动连接数据库获取相关模型类的数据字段信息,并缓存下来。
ThinkPHP支持PDO方式,如果要使用PDO方式连接数据库,可以参考下面的设置。
我们以项目配置文件定义为例来说明:
return array(
‘DB_TYPE’=> ‘pdo’,
// 注意DSN的配置针对不同的数据库有所区别 请参考PHP手册PDO类库部分
‘DB_DSN’=> ‘mysql:host=localhost;dbname=think’,
‘DB_USER’=>’root’,
‘DB_PWD’=>”,
‘DB_PREFIX’=>’think_’,
// 其他项目配置参数………
);
使用PDO方式的时候,要注意检查是否开启相关的PDO模块。DB_DSN参数仅对PDO方式连接才有效。

***5.3.7主从数据库
ThinkPHP的模型支持主从式数据库的连接,配置DB_DEPLOY_TYPE为1可以采用分布式数据库支持。如果采用分布式数据库,定义数据库配置信息的方式如下:
// 在项目配置文件里面定义
return array(
‘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′,
等效。
还可以设置分布式数据库的读写是否分离,默认的情况下读写不分离,也就是每台服务器都可以进行读写操作,对于主从式数据库而言,需要设置读写分离,通过下面的设置就可以:
‘DB_RW_SEPARATE’=>true,
在读写分离的情况下,第一个数据库配置是主服务器的配置信息,负责写入数据,其它的都是从数据库的配置信息,负责读取数据,数量不限制。每次连接从服务器并且进行读取操作的时候,系统会随机进行在从服务器中选择。
 注意事项:主从数据库的数据同步工作不在框架实现,需要数据库考虑自身的同步或者复制机制。

***5.3.8创建数据
在进行数据操作之前,我们往往需要手动创建需要的数据,例如对于提交的表单数据:
//获取表单的POST数据
$data[‘name’] = $_POST[‘name’];
$data[’email’] = $_POST[’email’];
//更多的表单数据值获取
……
然而ThinkPHP可以帮助你快速地创建数据对象,最典型的应用就是自动根据表单数据创建数据对象,这个优势在一个数据表的字段非常之多的情况下尤其明显。
很简单的例子:
//实例化User模型
$User = M(‘User’);
//根据表单提交的POST数据创建数据对象
$User->create();
//把创建的数据对象写入数据库
$User->add();
Create方法支持从其它方式创建数据对象,例如,从其它的数据对象,或者数组等
$data[‘name’] = ‘ThinkPHP';
$data[’email’] = ‘ThinkPHP@gmail.com’;
$User->create($data);
甚至还可以支持从对象创建新的数据对象
// 从User数据对象创建新的Member数据对象
$User = M(“User”);
$User->find(1);
$Member = M(“Member”);
$Member->create($User);
而事实上,create方法所做的工作远非这么简单,在创建数据对象的同时,完成了一些很有意义的工作,包括:
 支持多种数据源
 令牌验证
 数据自动验证
 字段映射支持
 字段类型检查
 数据自动完成
因此,我们熟悉的令牌验证、自动验证和自动完成(我们会在后面看到相关的用法)功能,其实都必须通过create方法才能生效。Create方法创建的数据对象是保存在内存中,并没有实际写入到数据库中,直到使用add或者save方法。如果只是想简单创建一个数据对象,并不需要完成一些额外的功能的话,可以使用data方法简单的创建数据对象。
使用如下:
//实例化User模型
$User = M(‘User’);
//创建数据后写入到数据库
$data[‘name’] = ‘ThinkPHP';
$data[’email’] = ‘ThinkPHP@gmail.com’;
$User->data($data)->add();
使用data方法创建的数据对象不会进行自动验证和过滤操作,请自行处理。但在进行add或者save操作的时候,数据表中不存在的字段以及非法的数据类型(例如对象、数组等非标量数据)是会自动过滤的,不用担心非数据表字段的写入导致SQL错误的问题。

***5.3.9字段映射
ThinkPHP的字段映射功能可以让你在表单中隐藏真正的数据表字段,而不用担心放弃TP的自动创建表单对象的功能,假设我们的User表里面有username和email字段,我们需要映射成另外的字段,定义方式如下:
Class UserModel extends Model{
protected $_map = array(
‘name’      =>’username’,
‘mail’                  =>’email’,
);
}
这样,在表单里面就可以直接使用name和mail名称作为表单数据提交了。在保存的时候会字段转换成定义的字段映射。

***5.3.10连贯操作
ThinkPHP2.0版本全面启用模型类的连贯操作方法,可以有效的提高数据存取的代码清晰度和开发效率。使用方面也比较简单, 假如我们现在要查询一个User表的满足状态为1的前10条记录,并希望按照用户的创建时间排序 ,代码如下:
$User->where(‘status=1′)->order(‘create_time’)->limit(10)->select();
除了select方法必须放到最后一个外,其他的连贯操作的方法调用顺序没有先后,例如,下面的代码和上面的等效:
$User->order(‘create_time’)->where(‘status=1′)->limit(10)->select();
如果不习惯使用连贯操作的话,新版还支持直接使用参数进行查询的方式。例如上面的代码可以改写为:
$User->select(array(‘order’=>’create_time’, ‘where’=>’status=1′, ‘limit’=>’10’));
使用数组参数方式的话,索引的名称就是连贯操作的方法名称。其实不仅仅是查询方法可以使用连贯操作,包括add、 save、delete等方法都可以使用,例如:
$User->where(‘id=1′)->field(‘id,name,email’)->find();
$User->where(‘status=1 and id=1′)->delete();
 
原则上说,所有的连贯操作都只有一个参数,并且连贯操作的参数仅在当此查询或者操作有效,完成后会自动清空连贯操作的所有传值,简而言之,连贯操作的结果不会带入以后的查询。下面总结下连贯操作的使用方法(更多的用法我们会在CURD操作的过程中详细描述):
Where方法:用于查询或者更新条件的定义
Where方法的参数支持字符串、数组和对象。详细的使用请参考后面的查询语言部分。
 
Table方法:定义要操作的数据表名称
可以动态改变当前操作的数据表名称,需要写数据表的全名,包含前缀,可以使用别名,例如:
$Model->Table(‘think_user user’)->where(‘status>1′)->select();
Table方法的参数支持字符串和数组,数组方式的用法:
$Model->Table(array(‘think_user’=>’user’,’think_group’=>’group’))->where(‘status>1′)->select();
使用数组方式定义的优势是可以避免因为表名和关键字冲突而出错的情况。
如果不定义table方法,默认会自动获取当前模型对应或者定义的数据表。
 
Data方法:数据对象赋值
可以用于新增或者保存数据之前的数据对象赋值,例如:
$Model->data($data)->add();
$Model->data($data)->where(‘id=3′)->save();
Data方法的参数支持对象和数组,如果是对象会自动转换成数组。如果不定义data方法赋值,也可以使用create方法或者手动给数据对象赋值的方式。
 
Field方法:定义要查询的字段
Field方法的参数支持字符串和数组,例如,
$Model->field(‘id,nickname as name’)->select();
$Model->field(array(‘id’,’nickname’=>’name’))->select();
如果不使用field方法指定字段的话,默认和使用field(‘*’)等效。
 
Order方法:结果排序
例如:order(‘id desc’)
排序方法支持对多个字段的排序
order(‘status desc,id asc’)
order方法的参数支持字符串和数组,数组的用法如下:
order(array(‘status’=>’desc’,’id’))
 
Limit方法:结果限制
我们知道不同的数据库类型的limit用法是不尽相同的,但是在ThinkPHP的用法里面始终是统一的方法,也就是limit(‘offset,length’),无论是Mysql、SqlServer还是Oracle数据库,都是这样使用,系统的数据库驱动类会负责解决这个差异化。
例如:
limit(‘1,10′)
如果使用limit(’10’)等效于limit(‘0,10′)
 
Page方法:查询分页
Page操作方法是新增的特性,可以更加快速的进行分页查询。
Page方法的用法和limit方法类似,格式为:
Page(‘page[,listRows]’)
Page表示当前的页数,listRows表示每页显示的记录数。例如:
Page(‘2,10′)
表示每页显示10条记录的情况下面,获取第2页的数据。
listRow如果不写的话,会读取limit(‘length’)的值,例如:
limit(25)->page(3);
表示每页显示25条记录的情况下面,获取第3页的数据。
如果limit也没有设置的话,则默认为每页显示20条记录。
 
Group方法:查询Group支持
例如:group(‘user_id’)
Group方法的参数只支持字符串
 
Having方法:查询Having支持
例如:having(‘user_id>0′)
having方法的参数只支持字符串
 
Join方法:查询Join支持
Join方法的参数支持字符串和数组,并且join方法是连贯操作中唯一可以多次调用的方法。
例如:
$Model->join(‘ work ON artist.id = work.artist_id’)->join(‘card ON artist.card_id = card.id’)->select();
默认采用LEFT JOIN方式,如果需要用其他的JOIN方式,可以改成
$Model->join(‘RIGHT JOIN work ON artist.id = work.artist_id’)->select();
如果join方法的参数用数组的话,只能使用一次join方法,并且不能和字符串方式混合使用。
例如:
join(array(‘ work ON artist.id = work.artist_id’,’card ON artist.card_id = card.id’))
 
Distinct方法:查询的Disiinct支持
查询数据的时候进行唯一过滤
$Model->Distinct(true)->select();
 
Relation方法:关联查询支持
关联查询方法的详细用法请参考后面的关联模型部分。
 
Lock方法:查询锁定
Lock方法是用于数据库的锁机制,如果在查询或者执行操作的时候使用:
Lock(true)
就会自动在生成的SQL语句最后加上FOR UPDATE。

***5.3.11CURD操作
ThinkPHP提供了灵活和方便的数据操作方法,对数据库操作的四个基本操作(CURD):创建、更新、读取和删除的实现是最基本的,也是必须掌握的,在这基础之上才能熟悉更多实用的数据操作方法。CURD操作通常是可以和连贯操作配合完成的。下面来分析下各自的用法:
(下面的CURD操作我们均以M方法创建模型实例来说明,因为不涉及到具体的业务逻辑)
一、创建操作
在ThinkPHP使用add方法新增数据到数据库。
使用方法如下:
$User = M(“User”); // 实例化User对象
$data[‘name’] = ‘ThinkPHP';
$data[’email’] = ‘ThinkPHP@gmail.com’;
$User->add($data);
或者使用data方法连贯操作
$User->data($data)->add();
如果在add之前已经创建数据对象的话(例如使用了create或者data方法),add方法就不需要再传入数据了。
使用create方法的例子:
$User = M(“User”); // 实例化User对象
// 根据表单提交的POST数据创建数据对象
$User->create();
$User->add(); // 根据条件保存修改的数据
如果你的主键是自动增长类型,并且如果插入数据成功的话,Add方法的返回值就是最新插入的主键值,可以直接获取。
 
二、读取数据
在ThinkPHP中读取数据的方式很多,通常分为读取数据和读取数据集。
读取数据集使用findall或者select方法(findall和select方法等效):
$User = M(“User”); // 实例化User对象
//查找status值为1的用户数据以创建时间排序返回10条数据
$list = $User->where(‘status=1′)->order(‘create_time’)->limit(10)->select();
select方法的返回值是一个二维数组,如果没有查询到任何结果的话,也是返回一个空的数组。配合上面提到的连贯操作方法可以完成复杂的数据查询。而最复杂的连贯方法应该是where方法的使用,因为这部分涉及的内容较多,我们会在查询语言部分就如何进行组装查询条件进行详细的使用说明。基本的查询暂时不涉及关联查询部分,而是统一采用关联模型来进行数据操作,这一部分请参考关联模型部分。
读取数据使用find方法:
读取数据的操作其实和数据集的类似,select可用的所有连贯操作方法也都可以用于find方法,区别在于find方法最多只会返回一条记录,因此limit方法对于find查询操作是无效的。
$User = M(“User”); // 实例化User对象
//查找status值为1name值为think的用户数据
$User->where(‘status=1 AND name=”think” ‘)->find();
即使满足条件的数据不止一条,find方法也只会返回第一条记录。
 
如果要读取某个字段的值,可以使用getField方法,例如:
$User = M(“User”); // 实例化User对象
//获取ID为3的用户的昵称
$nickname = $User->where(‘id=3′)->getField(‘nickname’);
当只有一个字段的时候,始终返回一个值。
如果传入多个字段的话,可以返回一个关联数组:
$User = M(“User”); // 实例化User对象
//获取所有用户的ID和昵称列表
$list = $User->getField(‘id,nickname’);
返回的list是一个数组,键名是用户的id, 键值是用户的昵称nickname。
 
三、更新数据
在ThinkPHP中使用save方法更新数据库,并且也支持连贯操作的使用。
$User = M(“User”); // 实例化User对象
// 要修改的数据对象属性赋值
$data[‘name’] = ‘ThinkPHP';
$data[’email’] = ‘ThinkPHP@gmail.com’;
$User->where(‘id=5′)->save($data); // 根据条件保存修改的数据
为了保证数据库的安全,避免出错更新整个数据表,如果没有任何更新条件,数据对象本身也不包含主键字段的话,save方法不会更新任何数据库的记录。
因此下面的代码不会更改数据库的任何记录
$User->save($data);
除非使用下面的方式:
$User = M(“User”); // 实例化User对象
// 要修改的数据对象属性赋值
$data[‘id’] = 5;
$data[‘name’] = ‘ThinkPHP';
$data[’email’] = ‘ThinkPHP@gmail.com’;
$User->save($data); // 根据条件保存修改的数据
如果id是数据表的主键的话,系统自动会把主键的值作为更新条件来更新其他字段的值。
还有一种方法是通过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方法支持同时更新多个字段,只需要传入数组即可,例如:
$User = M(“User”); // 实例化User对象
// 更改用户的name和email的值
$User-> where(‘id=5′)->setField(array(‘name’,’email’),array(‘ThinkPHP’,’ThinkPHP@gmail.com’));
而对于统计字段(通常指的是数字类型)的更新,系统还提供了setInc和setDec方法:
$User = M(“User”); // 实例化User对象
$User->setInc(‘score’,’id=5′,3);// 用户的积分加3
$User->setInc(‘score’,’id=5′); // 用户的积分加1
$User->setDec(‘score’,’id=5′,5);// 用户的积分减5
$User->setDec(‘score’,’id=5′); // 用户的积分减1
 
四、删除数据
在ThinkPHP中使用delete方法删除数据库中的记录。同样可以使用连贯操作进行删除操作。
$User = M(“User”); // 实例化User对象
$User->where(‘id=5′)->delete(); // 删除id为5的用户数据
$User->where(‘status=0′)->delete(); // 删除所有状态为0的用户数据
delete方法可以用于删除单个或者多个数据,主要取决于删除条件,也就是where方法的参数,也可以用order和limit方法来限制要删除的个数,例如:
// 删除所有状态为0的5个用户数据按照创建时间排序
$User->where(‘status=0′)->order(‘create_time’)->limit(‘5′)->delete();

***5.3.12ActiveRecord
ThinkPHP实现了ActiveRecords模式的ORM模型,采用了非标准的ORM模型:表映射到类,记录映射到对象。最大的特点就是使用方便和便于理解(因为采用了对象化),提供了开发的最佳体验,从而达到敏捷开发的目的。下面我们用AR模式来换一种方式重新完成CURD操作。
 
创建数据
$User = M(“User”); // 实例化User对象
// 然后直接给数据对象赋值
$User->name = ‘ThinkPHP';
$User->email = ‘ThinkPHP@gmail.com’;
// 把数据对象添加到数据库
$User->add();
如果使用了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”);
这个作为查询语言来说是最为直观的,如果查询成功,查询的结果直接保存在当前的数据对象中,在进行下一次查询操作之前,我们都可以提取,例如获取查询的结果数据:
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(); // 删除当前的数据对象
或者直接根据主键进行删除
$User->delete(‘8′); // 删除主键为8的数据
$User->delete(‘5,6′); // 删除主键为5、6的多个数据

***5.3.13令牌验证
ThinkPHP新版内置了表单令牌验证功能,可以有效防止表单的远程提交等安全防护。
表单令牌验证相关的配置参数有:
‘TOKEN_ON’=>true,  //是否开启令牌验证
‘TOKEN_NAME’=>’__hash__’,    // 令牌验证的表单隐藏字段名称
‘TOKEN_TYPE’=>’md5′,  //令牌哈希验证规则默认为MD5
如果开启表单令牌验证功能,系统会自动在带有表单的模板文件里面自动生成以TOKEN_NAME为名称的隐藏域,其值则是TOKEN_TYPE方式生成的哈希字符串,用于实现表单的自动令牌验证。
自动生成的隐藏域位于表单Form结束标志之前,如果希望自己控制隐藏域的位置,可以手动在表单页面添加{__TOKEN__}标识,系统会在输出模板的时候自动替换。如果在开启表单令牌验证的情况下,个别表单不需要使用令牌验证功能,可以在表单页面添加{__NOTOKEN__},则系统会忽略当前表单的令牌验证。
如果页面中存在多个表单,建议添加{__TOKEN__}标识,并确保只有一个表单需要令牌验证。
模型类在创建数据对象的同时会自动进行表单令牌验证操作,如果你没有使用create方法创建数据对象的话,则需要手动调用模型的autoCheckToken方法进行表单令牌验证。如果返回false,则表示表单令牌验证错误。例如:
$User = M(“User”); // 实例化User对象
//手动进行令牌验证
if (!$User->autoCheckToken($_POST)){
//令牌验证错误
}

***5.3.14类型检测
新版的ThinkPHP具有字段类型检测,对于不合法的字段数据会进行强制转换。字段类型检测可以用于数据写入和数据查询操作。
需要启用字段类型检测的话,需要在配置文件中开启DB_FIELDTYPE_CHECK参数:
‘DB_FIELDTYPE_CHECK’=>true,  //开启字段类型验证
如果在非调试模式下面开启字段类型检测后,请清空字段缓存目录(位于Runtime/Data/_fields/),重新生成字段缓存的时候,会在缓存文件中记录字段的类型信息。这是后面进行字段类型检测的前提。
字段类型检测主要在两个阶段会自动处理:
一、在数据写入到数据库之前
例如:
$User = M(“User”); // 实例化User对象
// 然后直接给数据对象赋值
$User->name = ‘ThinkPHP';
$User->score = ‘2ThinkPHP';
// 把数据对象添加到数据库
$User->add();
由于用户表的score设计的是数字类型,所以实际写入数据库之前,score属性的值已经被强制进行intval转换了,模型的save方法也会同样进行字段类型检查。虽然在很多情况下,数据库本身也会进行数据转换,但是对于某些数据库要求严格检查数据类型的情况会有帮助。
 
二、在使用数组方式的普通查询条件后
例如:
$User = M(“User”); // 实例化User对象
$condition[‘id’] = ‘1 OR 1=1′;
// 把查询条件传入查询方法
$User->where($condition)->select();
对于这样的一个查询条件,在进行数据库查询之前,会对查询的数组条件进行字段类型检查,直接就把id的值强制转换为1然后再进行查询操作。
即使不进行强制转换,系统也会进行安全过滤,把这样的非法数据进行转义,区别在于这样对于数据库更加安全,对于某些数据库要求严格检查数据类型的情况会有帮助。

***5.3.15自动验证
类型检查只是针对数据库级别的验证,所以系统还内置了数据对象的自动验证功能来完成模型的业务规则验证,而大多数情况下面,数据对象是由表单提交的$_POST数据创建。需要使用系统的自动验证功能,只需要在Model类里面定义$_validate属性,是由多个验证因子组成的数组,支持的验证因子格式:
array(验证字段,验证规则,错误提示,验证条件,附加规则,验证时间)
验证字段:需要验证的表单字段名称,这个字段不一定是数据库字段,也可以是表单的一些辅助字段,例如确认密码和验证码等等。(必须)
验证规则: 要进行验证的规则,需要结合附加规则(必须)
提示信息: 用于验证失败后的提示信息定义(必须)
验证条件:(可选)
 Model::EXISTS_TO_VAILIDATE或者0存在字段就验证 (默认)
 Model::MUST_TO_VALIDATE或者1必须验证
 Model::VALUE_TO_VAILIDATE或者2值不为空的时候验证
附加规则: 配合验证规则使用(可选),包括:
 regex使用正则进行验证,表示前面定义的验证规则是一个正则表达式(默认)
 function使用函数验证,前面定义的验证规则是一个函数名
 callback使用方法验证,前面定义的验证规则是当前Model类的一个方法
 confirm验证表单中的两个字段是否相同,前面定义的验证规则是一个字段名
 equal验证是否等于某个值,该值由前面的验证规则定义
 in验证是否在某个范围内,前面定义的验证规则必须是一个数组
 unique验证是否唯一,系统会根据字段目前的值查询数据库来判断是否存在相同的值
系统还内置了一些常用正则验证的规则,可以直接使用,包括:require字段必须、email邮箱、url URL地址、currency货币、number数字,这些验证规则可以直接使用。
验证时间:(可选)
Model:: MODEL_INSERT或者1新增数据时候验证
Model:: MODEL_UPDATE或者2编辑数据时候验证
Model:: MODEL_BOTH或者3全部情况下验证(默认)
 
示例:
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{
//验证通过 可以进行其他数据操作
}
通常来说,每个数据表对应的验证规则是相对固定的,但是有些特殊的情况下面可能会改变验证规则,我们可以动态的改变验证规则来满足不同条件下面的验证:
$User = D(“User”); // 实例化User对象
$validate    =    array(
array(‘verify’,’require’,’验证码必须!’), //仅仅需要进行验证码的验证
);
$User-> setProperty(“_validate”,$validate);
$result = $User->create();
if (!$result){
// 如果创建失败 表示验证没有通过 输出错误提示信息
exit($User->getError());
}else{
//验证通过 可以进行其他数据操作
}

***
5.3.16自动完成
在Model类定义 $_auto属性,可以完成数据自动处理功能,用来处理默认值、数据过滤以及其他系统写入字段。$_auto属性是由多个填充因子组成的数组,填充因子定义格式:
array(填充字段,填充内容,填充条件,附加规则)
填充字段就是需要进行处理的表单字段,这个字段不一定是数据库字段,也可以是表单的一些辅助字段,例如确认密码和验证码等等。
填充条件包括:
 Model:: MODEL_INSERT或者1新增数据的时候处理(默认)
 Model:: MODEL_UPDATE或者2更新数据的时候处理
 Model:: MODEL_BOTH或者3所有情况都进行处理
附加规则包括:
 function:使用函数,表示填充的内容是一个函数名
 callback:回调方法 ,表示填充的内容是一个当前模型的方法
 field:用其它字段填充,表示填充的内容是一个其他字段的值
 string:字符串(默认方式)
示例:
protected $_auto = array (
array(‘status’,’1′),  // 新增的时候把status字段设置为1
array(‘password’,’md5′,1,’function’) , // 对password字段在新增的时候使md5函数处理
array(‘name’,’getName’,1,’callback’), // 对name字段在新增的时候回调getName方法
array(‘create_time’,’time’,2,’function’), // 对create_time字段在更新的时候写入当前时间戳
);
使用自动填充可能会覆盖表单提交项目。其目的是为了防止表单非法提交字段。使用Model类的create方法创建数据对象的时候会自动进行表单数据处理。
和自动验证一样,自动完成机制需要使用create方法才能生效。并且,也可以在操作方法中动态的更改自动完成的规则。
 $auto = array (
array(‘password’,’md5′,1,’function’) // 对password字段在新增的时候使md5函数处理
);
$User-> setProperty(“_auto”,$auto);
$User->create();

***5.3.17查询语句
***5.3.17.1普通查询
除了字符串查询条件外,数组和对象方式的查询条件是非常常用的,这些是基本查询所必须掌握的。
一、使用数组作为查询条件
$User = M(“User”); // 实例化User对象
$condition[‘name’] = ‘thinkphp';
// 把查询条件传入查询方法
$User->where($condition)->select();
 
二、使用对象方式来查询 可以使用任何对象 这里以stdClass内置对象为例
$User = M(“User”); // 实例化User对象
//定义查询条件
$condition = new stdClass();
$condition->name = ‘thinkphp';  // 查询name的值为thinkphp的记录
$User->where($condition)->select();
// 上面的查询条件等同于 where(‘name=”thinkphp”‘)
使用对象方式查询和使用数组查询的效果是相同的,并且是可以互换的。
 
三、使用查询表达式
上面的查询条件仅仅是一个相等的判断,可以使用查询表达式支持更多的SQL语法,并且可以用于数组或者对象方式的查询(下面仅以数组方式为例说明),查询表达式的使用格式:
$map[‘字段名’]  = array(‘表达式’, ‘查询条件’);
表达式不分大小写,支持的查询表达式有下面几种,分别表示的含义是:
EQ:等于(=)
例如:$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
LT:小于(<)
例如:$map[‘id’]  = array(‘lt’,100);
表示的查询条件就是 id < 100
ELT: 小于等于(<=)
例如:$map[‘id’]  = array(‘elt’,100);
表示的查询条件就是 id <= 100
LIKE: 同sql的LIKE
例如:$map[‘name’] =array(‘like’,’thinkphp%’);
查询条件就变成 name like ‘thinkphp%’
如果配置了DB_LIKE_FIELDS参数的话,某些字段也会自动进行模糊查询。例如设置了:
‘DB_LIKE_FIELDS’=>’title|content’
的话,使用
$map[‘title’] =’thinkphp';
查询条件就会变成 name like ‘%thinkphp%’
[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)
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); // 根据条件保存修改的数据

***5.3.17.2区间查询
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)
$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(‘like’,’%c%’), ‘ThinkPHP’,’or’);
最后的查询条件是:
(`name` LIKE ‘%a%’) OR (`name` LIKE ‘%b%’) OR (`name` LIKE ‘%c%’) OR (`name` = ‘ThinkPHP’)

***5.3.17.3组合查询
如果进行多字段查询,那么字段之间的默认逻辑关系是 逻辑与 AND,但是用下面的规则可以更改默认的逻辑判断,例如下面的查询条件:
$User = M(“User”); // 实例化User对象
$map[‘id’] = array(‘neq’,1);
$map[‘name’] =’ok';
$User->where($map)->select();
得到的查询条件是:( `id` != 1 ) AND ( `name` = ‘ok’ )
如果添加了下面的查询条件:
$map[‘_logic’] =’or';
 现在的查询条件就变为: ( `id` != 1 ) OR ( `name` = ‘ok’ )
数组条件还可以和字符串条件混合使用,例如:
$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 )
新版还可以支持一种特殊的条件查询,前提是简单的条件相等判断。
$map[‘_query’] = ‘status=1&score=100&_logic=or';
得到的查询条件是:`status` = ‘1’ OR `score` = ‘100’

***5.3.17.4复合查询
新版完善了复合查询,可以完成比较复杂的查询条件组装。
例如:
$where[‘name’] = array(‘like’, ‘%thinkphp%’);
$where[‘title’] = array(‘like’, ‘%thinkphp%’);
$where[‘_logic’]=’or';
$map[‘_complex’] = $where;
$map[‘id’]  = array(‘gt’,1);
查询条件是
( id > 1) AND ( ( name like ‘%thinkphp%’) OR ( title like ‘%thinkphp%’) )
复合查询使用了_complex作为子查询条件来定义,配合之前的查询方式,可以非常灵活的制定更加复杂的查询条件
相同的查询条件有多种表达形式,例如上面的查询条件可以改成:
$where[‘id’] = array(‘gt’,1);
$where[‘_string’] = ‘ (name like “%thinkphp%”)  OR ( title like “%thinkphp”) ‘;
最后生成的SQL语句是一致的。

***5.3.17.5统计查询
在应用中我们经常会用到一些统计数据,例如当前所有(或者满足某些条件)的用户数、所有用户的最大积分、用户的平均成绩等等,ThinkPHP为这些统计操作提供了一系列的内置方法。
$User = M(“User”); // 实例化User对象
 获取用户数:
$userCount = $User->count();
 获取用户的最大积分:
$maxScore = $User->max(‘score’);
 获取积分大于0的用户的最小积分:
$minScore = $User->where(‘score>0′)->min(‘score’);
获取用户的平均积分:
$avgScore = $User->avg(‘score’);
统计用户的总成绩:
$sumScore = $User->sum(‘score’);
并且所有的统计查询均支持连贯操作的使用。

***5.3.17.6定位查询
ThinkPHP支持定位查询,可以使用getN方法直接返回查询结果中的某个位置的记录。例如:
 获取符合条件的第3条记录:
$User->where(‘score>0′)->order(‘score desc’)->getN(2);
 获取符合条件的最后第二条记录:
$User-> where(‘score>80′)->order(‘score desc’)->getN(-2);
 获取第一条记录:
$User->where(‘score>80′)->order(‘score desc’)->first();
 获取最后一条记录:
$User->where(‘score>80′)->order(‘score desc’)->last();

***5.3.17.7SQL查询
ThinkPHP内置的ORM和ActiveRecord模式实现了方便的数据存取操作,而且新版增加的连贯操作功能更是让这个数据操作更加清晰,但是ThinkPHP仍然保留了原生的SQL查询和执行操作支持,为了满足复杂查询的需要和一些特殊的数据操作,SQL查询的返回值因为是直接返回的Db类的查询结果,没有做任何的处理。而且可以支持查询缓存。主要包括下面两个方法:
1、query方法
query方法是用于sql查询操作,和select一样返回数据集,例如:
$Model = new Model() // 实例化一个model对象 没有对应任何数据表
$Model->query(“select * from think_user where status=1″);
 
2、execute方法
用于更新和写入数据的sql操作,返回影响的记录数,例如:
$Model = new Model() // 实例化一个model对象 没有对应任何数据表
$Model->execute(“update think_user set name=’thinkPHP’ where status=1″);
 
关于原生SQL操作的一点补充
通常使用原生SQL需要手动加上当前要查询的表名,如果你的表名以后会变化的话,那么就需要修改每个原生SQL查询的sql语句了,针对这个情况,TP还提供了一个小的技巧来帮助解决这个问题。
例如:
$model = M(“User”);
$model->query(‘select * from __TABLE__ where status>1′);
我们这里使用了__TABLE__这样一个字符串,系统在解析的时候会自动替换成当前模型对应的表名,这样就可以做到即使模型对应的表名有所变化,仍然不用修改原生的sql语句。

***5.3.17.8动态查询
借助PHP5语言的特性,ThinkPHP实现了动态查询。该查询方式针对数据表的字段进行查询。例如,User对象拥有id,name,email,address等属性,那么我们就可以使用下面的查询方法来直接根据某个属性来查询符合条件的记录。
$user = $User->getByName(‘liu21st’);
$user = $User->getByEmail(‘liu21st@gmail.com’);
$user = $User->getByAddress(‘中国深圳’);
暂时不支持多数据字段的动态查询方法,请使用find方法和select方法进行查询。ThinkPHP还提供了另外一种动态查询方式,就是获取符合条件的前N条记录。例如,我们需要获取当前用户中积分大于0,积分最高的前5位用户 :
$User-> where(‘score>80′)->order(‘score desc’)->top5();
要获取积分的前8位可以改成:
$User-> where(‘score>80′)->order(‘score desc’)->top8();

***5.3.18查询锁定
ThinkPHP支持查询或者更新的锁定,只需要在查询或者更新之前使用lock方法即可。
查询锁定使用:
$list = $User->lock(true)->where(‘status=1′)->order(‘create_time’)->limit(10)->select();
更新锁定使用:
$list = $User->lock(true)->where(‘status=1′)->data($data)->save();

***5.3.19事务支持
ThinkPHP提供了单数据库的事务支持,如果要在应用逻辑中使用事务,可以参考下面的方法:
启动事务:
$User->startTrans()
 提交事务:
$User->commit()
 事务回滚:
$User->rollback()
事务是针对数据库本身的,所以可以跨模型操作的 。
例如:
// 在User模型中启动事务
$User->startTrans()
//进行相关的业务逻辑操作
$Info = M(“Info”); // 实例化Info对象
$Info->save($User); //保存用户信息
if (操作成功){
// 提交事务
$User->commit()
}else{
// 事务回滚
$User->rollback()
}

***5.3.20高级模型
***5.3.20.1字段过滤
基础模型类内置有数据自动完成功能,可以对字段进行过滤,但是必须通过Create方法调用才能生效。高级模型类的字段过滤功能却可以不受create方法的调用限制,可以在模型里面定义各个字段的过滤机制,包括写入过滤和读取过滤。
字段过滤的设置方式只需要在Model类里面添加 $_filter属性,并且加入过滤因子,格式如下:
protected $_filter = array(
‘过滤的字段’=>array(‘写入过滤规则’,’读取过滤规则’,是否传入整个数据对象),
)
过滤的规则是一个函数,如果设置传入整个数据对象,那么函数的参数就是整个数据对象,默认是传入数据对象中该字段的值。
举例说明,例如我们需要在发表文章的时候对文章内容进行安全过滤,并且希望在读取的时候进行截取前面255个字符,那么可以设置:
protected $_filter = array(
‘content’=>array(‘contentWriteFilter’,’contentReadFilter’),
)
其中,contentWriteFilter是自定义的对字符串进行安全过滤的函数,而contentReadFilter是自定义的一个对内容进行截取的函数。通常我们可以在项目的公共函数文件里面定义这些函数。

***5.3.20.2序列化字段
序列化字段是新版推出的新功能,可以用简单的数据表字段完成复杂的表单数据存储,尤其是动态的表单数据字段。
要使用序列化字段的功能,只需要在模型中定义serializeField属性,定义格式如下:
protected $serializeField = array(
 ‘info’ => array(‘name’, ’email’, ‘address’),
);
Info是数据表中的实际存在的字段,保存到其中的值是name、email和address三个表单字段的序列化结果。序列化字段功能可以在数据写入的时候进行自动序列化,并且在读出数据表的时候自动反序列化,这一切都无需手动进行。
下面还是是User数据表为例,假设其中并不存在name、email和address字段,但是设计了一个文本类型的info字段,那么下面的代码是可行的:
$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和address属性
//输出序列化字段
echo $User->name;
echo $User->email;
echo $User->address;

***5.3.20.3文本字段
ThinkPHP支持数据模型中的个别字段采用文本方式存储,这些字段就称为文本字段,通常可以用于某些Text或者Blob字段,或者是经常更新的数据表字段。
要使用文本字段非常简单,只要在模型里面定义blobFields属性就行了。例如,我们需要对Blog模型的content字段使用文本字段,那么就可以使用下面的定义:
Protected  $blobFields  =       array(‘content’);
系统在查询和写入数据库的时候会自动检测文本字段,并且支持多个字段的定义。
需要注意的是:对于定义的文本字段并不需要数据库有对应的字段,完全是另外的。而且,暂时不支持对文本字段的搜索功能。

***5.3.20.4只读字段
只读字段用来保护某些特殊的字段值不被更改,这个字段的值一旦写入,就无法更改。
要使用只读字段的功能,我们只需要在模型中定义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的值仍然还是更新之前的值。

***5.3.20.5多数据库连接和切换
分布式数据库的配置信息是定义在配置文件里面的,所以一般情况下是无法更改的。另外使用分布式数据库有个不足,就是无法同时连接多个不同类型的数据库。
多数据库支持
如果你的应用需要在特殊的时候连接多个数据库,那么可以尝试使用ThinkPHP的多数据库连接特性:包括相同类型的数据库和不同类型的数据库。
我们首先需要在模型类里面增加需要的数据库连接,例如:
我们在UserModel类增加多个数据库连接,首先定义额外的数据库连接信息
$myConnect1 = array(
‘dbms’     => ‘mysql’, 
‘username’ => ‘username’, 
‘password’ => ‘password’, 
‘hostname’ => ‘localhost’, 
‘hostport’ => ‘3306’, 
‘database’ => ‘dbname’
);
或者使用下面的定义
$myConnect1 = ‘mysql://username:passwd@localhost:3306/DbName';
定义之后就可以进行动态的增加和切换数据库了。
$User = D(“User”);
// 增加数据库连接 第二个参数表示连接的序号
// 注意内置的数据库连接序号是0,所以额外的数据库连接序号应该从1开始
$User->addConnect($myConnect1,1);
// 可以同时增加多个数据库连接 myConnect2和myConnect3的定义方式同myConnect1
$User->addConnect($myConnect1,1);
$User->addConnect($myConnect2,2);
$User->addConnect($myConnect3,3);
这样在UserModel里面就同时存在了4个数据库(加上项目配置里面定义的)连接。那么我们如何使用这些不同的数据库连接呢?ThinkPHP采用了灵活的切换机制,由应用来控制不同的数据库连接。例如,我们需要在其中一个应用里面用到$myConnect2这个数据库连接,那么用下面的方法切换即可:
$User->switchConnect(2);
switchConnect方法会智能识别该连接是否是相同类型的连接。
如果要切换的数据表名称和当前模型的不一致,可以传入参数:
$User->switchConnect(2, ‘Member’);
这样连接新的数据库后切换到的数据表就成了member表了,当然前缀还是一样的。
我们还可以使用addConnect方法添加多个动态数据库连接,只要传入数组参数就可以了,例如:
$myConnect[1] = ‘mysql://username:passwd@192.168.1.1:3306/DbName1′;
$myConnect[2] = ‘mysqli://username:passwd@192.168.1.2:3306/DbName2′;
$myConnect[3] = ‘mysql://username:passwd@192.168.1.3:3306/DbName3′;
$User->addConnect($myConnect);
如果需要删除之前动态添加的连接,可以使用delConnect方法,例如:
// 删除连接序号为2的数据库连接
$User->delConnect(2);
可以在使用之后关闭添加的连接,可以使用closeConnect方法,例如:
// 关闭连接序号为3的数据库连接
$User->closeConnect(3);

***5.3.20.6悲观锁和乐观锁
业务逻辑的实现过程中,往往需要保证数据访问的排他性。如在金融系统的日终结算处理中,我们希望针对某个时间点的数据进行处理,而不希望在结算进行过程中(可能是几秒种,也可能是几个小时),数据再发生变化。此时,我们就需要通过一些机制来保证这些数据在某个操作过程中不会被外界修改,这样的机制,在这里,也就是所谓的 “ 锁 ” ,即给我们选定的目标数据上锁,使其无法被其他程序修改。 ThinkPHP支持两种锁机制:即通常所说的 “ 悲观锁( Pessimistic Locking) ”和 “ 乐观锁( Optimistic Locking) ” 。
悲观锁( Pessimistic Locking)
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。 通常是使用for update子句来实现悲观锁机制。
ThinkPHP支持悲观锁机制,默认情况下,是关闭悲观锁功能的,要在查询和更新的时候启用悲观锁功能,可以通过使用之前提到的查询锁定方法,例如:
$User->lock(true)->save($data); // 使用悲观锁功能
 
乐观锁( Optimistic Locking)
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。 如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情况将导致怎样的后果。乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。
ThinkPHP也可以支持乐观锁机制,要启用乐观锁,只需要继承高级模型类并定义模型的optimLock属性,并且在数据表字段里面增加相应的字段就可以自动启用乐观锁机制了。默认的optimLock属性是lock_version,也就是说如果要在User表里面启用乐观锁机制,只需要在User表里面增加lock_version字段,如果有已经存在的其它字段作为乐观锁用途,可以修改模型类的optimLock属性即可。如果存在optimLock属性对应的字段,但是需要临时关闭乐观锁机制,把optimLock属性设置为false就可以了。

***5.3.20.7延迟更新
我们经常需要给某些数据表添加一些需要经常更新的统计字段,例如用户的积分、文件的下载次数等等,而当这些数据更新的频率比较频繁的时候,数据库的压力也随之增大不少,我们可以利用高级模型的延迟更新功能缓解。
延迟更新功能是指我们可以给统计字段的更新设置一个延迟时间,在这个时间段内所有的更新会被累积缓存起来,然后定时地统一更新数据库。这比较适合某个字段经常需要递增或者递减,并且对实时性要求没有那么严格的情况。
我们先来看递增的情况,如果我们需要给会员累积积分,可以使用
$User = D(“User”); // 实例化User对象
// 把id为5的用户的积分加10
$User->setInc(“score”,”id=5″,10);
$User->setInc(“score”,”id=5″,30);
上面的操作更新了两次用户积分,并且都实时保存到数据库
如果我们使用延迟更新方法,例如下面对用户的积分延迟更新60秒
$User->setLazyInc(“score”,”id=5″,10,60);
$User->setLazyInc(“score”,”id=5″,30,60);
$User->setLazyInc(“score”,”id=5″,10,60);
那么60秒内执行的所有积分更新操作都会被延迟,实际会在60秒后统一更新积分到数据库,而不是每次都更新数据库。临时积分会被累积并缓存起来,最后到了延迟更新时间,再统一更新。相当于在60秒后执行了:
$User->setInc(“score”,”id=5″,50);
效果是等效。区别在于用户数据库中的积分不是实时的。
同样,还可以使用setLazyDec进行延迟更新操作。

***5.3.20.8数据分表
对于大数据量的应用,经常会对数据进行分表,有些情况是可以利用数据库的分区功能,但并不是所有的数据库或者版本都支持,因此我们可以利用ThinkPHP内置的数据分表功能来实现。帮助我们更方便的进行数据的分表和读取操作。
和数据库分区功能不同,内置的数据分表功能需要根据分表规则手动创建相应的数据表。
在需要分表的模型中定义partition属性即可。
protected $partition = array(
 ‘field’ => ‘name’,// 要分表的字段通常数据会根据某个字段的值按照规则进行分表
 ‘type’ => ‘md5′,// 分表的规则包括id year mod md5函数和首字母
 ‘expr’ => ‘name’,// 分表辅助表达式可选配合不同的分表规则
 ‘num’ => ‘name’,// 分表的数目可选实际分表的数量
);
定义好了分表属性后,我们就可以来进行CURD操作了,唯一不同的是,获取当前的数据表不再使用getTableName方法,而是使用getPartitionTableName方法,而且必须传入当前的数据。然后根据数据分析应该实际操作哪个数据表。因此,分表的字段值必须存在于传入的数据中,否则会进行联合查询。

***5.3.20.9返回类型
系统默认的数据库查询返回的是数组,我们可以给单个数据设置返回类型,以满足特殊情况的需要,例如:
$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数据进行处理
}
}

***5.3.21视图模型
***5.3.21.1视图定义
视图通常是指数据库的视图,视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。对其中所引用的基础表来说,视图的作用类似于筛选。定义视图的筛选可以来自当前或其它数据库的一个或多个表,或者其它视图。分布式查询也可用于定义使用多个异类源数据的视图。如果有几台不同的服务器分别存储组织中不同地区的数据,而您需要将这些服务器上相似结构的数据组合起来,这种方式就很有用。
视图在有些数据库下面并不被支持,但是ThinkPHP模拟实现了数据库的视图,该功能可以用于多表联合查询。非常适合解决HAS_ONE和 BELONGS_TO类型的关联查询。
要定义视图模型,只需要继承ViewModel,然后设置viewFields属性即可。例如下面的例子,我们定义了一个BlogView模型对象,其中包括了Blog模型的id、name、title和User模型的name,以及Category模型的title字段,我们通过创建BlogView模型来快速读取一个包含了User名称和类别名称的Blog记录(集)。
class BlogViewModel extends ViewModel
{
 public $viewFields = array(
 ‘Blog’=>array(‘id’,’name’,’title’),
 ‘Category’=>array(‘title’=>’category_name’, ‘_on’=>’Blog.category_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模型对应的数据表定义了一个别名。如果希望给blog表定义另外的别名,可以使用
‘_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语句就是
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_user 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模型的
‘_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.user_id=User.id
我们可以在试图模型里面定义特殊的字段,例如下面的例子定义了一个统计字段
‘Category’=>array(‘title’=>’category_name’,’COUNT(Blog.id)’=>’count’,’_on’=>’Category.id=Blog.category_id’),

***5.3.21.2视图查询
接下来,我们就可以和使用普通模型一样对视图模型进行操作了 。
$Model = D(“BlogView”);
$Model->field(‘id,name,title,category_name,useruame’)->where(‘id>10′)->order(‘id desc’)->select();
看起来和普通的模型操作并没有什么大的区别,可以和使用普通模型一样进行查询。如果发现查询的结果存在重复数据,还可以使用group方法来处理。
$Model->field(‘id,name,title,categoryName,userName’)->order(‘id desc’)->group(‘id’)->select();
我们可以看到,即使不定义视图模型,其实我们也可以通过方法来操作,但是显然非常繁琐。
$Model = D(“Blog”);
$Model->table(
‘think_blog Blog,
think_category Category,
think_user User’)
->field(
‘Blog.id,Blog.name,
Blog.title,
Category.title as category_name,
User.name as username’)
->order(‘Blog.id desc’)
->where(‘Blog.category_id=Category.id AND Blog.user_id=User.id’)
->select();
而定义了视图模型之后,所有的字段会进行自动处理,添加表别名和字段别名,从而简化了原来视图的复杂查询。

***5.3.22关联模型
***5.3.22.1关联关系
通常我们所说的关联关系包括下面三种:
 一对一关联 :ONE_TO_ONE,包括HAS_ONE 和 BELONGS_TO
 一对多关联 :ONE_TO_MANY,包括HAS_MANY 和 BELONGS_TO
 多对多关联 :MANY_TO_MANY
关联关系必然有一个参照表,例如:
有一个员工档案管理系统项目,这个项目要包括下面的一些数据表:基本信息表、员工档案表、部门表、项目组表、银行卡表(用来记录员工的银行卡资料)。
这些数据表之间存在一定的关联关系,我们以员工基本信息表为参照来分析和其他表之间的关联:
每个员工必然有对应的员工档案资料,所以属于HAS_ONE关联;
每个员工必须属于某个部门,所以属于BELONGS_TO关联;
每个员工可以有多个银行卡,但是每张银行卡只可能属于一个员工,因此属于HAS_MANY关联;
每个员工可以同时在多个项目组,每个项目组同时有多个员工,因此属于MANY_TO_MANY关联;
分析清楚数据表之前的关联关系后,我们才可以进行关联定义和关联操作。
***5.3.22.2关联定义
ThinkPHP可以很轻松的完成数据表的关联CURD操作,目前支持的关联关系包括下面四种:HAS_ONE、BELONGS_TO、HAS_MANY、MANY_TO_MANY。
一个模型根据业务模型的复杂程度可以同时定义多个关联,不受限制,所有的关联定义都统一在模型类的 $_link成员变量里面定义,并且可以支持动态定义。要支持关联操作,模型类必须继承RelationModel类,关联定义的格式是:
protected $_link = array(
    ‘关联1′  =>  array(
        ‘关联属性1′ => ‘定义’,
        ‘关联属性N’ => ‘定义’,
    ),
    ‘关联2′  =>  array(
        ‘关联属性1′ => ‘定义’,
        ‘关联属性N’ => ‘定义’,
    ),
    …
);
下面我们首先来分析下各个关联方式的定义:
HAS_ONE
HAS_ONE关联表示当前模型拥有一个子对象,例如,每个员工都有一个人事档案。我们可以建立一个用户模型UserModel,并且添加如下关联定义:
class UserModel extends RelationModel
{
         public $_link = array(
       ‘Profile’=> HAS_ONE,
);
}
上面是最简单的方式,表示其遵循了系统内置的数据库规范,完整的定义方式是:
class UserModel extends RelationModel
{
         public $_link = array(
        ‘Profile’=>array(
‘mapping_type’    =>HAS_ONE,
                 ‘class_name’    =>’Profile’,
                    // 定义更多的关联属性
                             ……
                  ),
);
}
关联HAS_ONE支持的关联属性有:
mapping_type关联类型,这个在HAS_ONE关联里面必须使用HAS_ONE常量定义。
class_name要关联的模型类名
例如,class_name定义为Profile的话则表示和另外的Profile模型类关联,这个Profile模型类是无需定义的,系统会自动定位到相关的数据表进行关联。
mapping_name关联的映射名称,用于获取数据用
该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。如果mapping_name没有定义的话,会取class_name的定义作为mapping_name。如果class_name也没有定义,则以数组的索引作为mapping_name。
foreign_key关联的外键名称
外键的默认规则是当前数据对象名称_id,例如:
UserModel对应的可能是表think_user(注意:think只是一个表前缀,可以随意配置)
那么think_user表的外键默认为 user_id,如果不是,就必须在定义关联的时候显式定义 foreign_key。
condition关联条件
关联查询的时候会自动带上外键的值,如果有额外的查询条件,可以通过定义关联的condition属性。
mapping_fields关联要查询的字段
默认情况下,关联查询的关联数据是关联表的全部字段,如果只是需要查询个别字段,可以定义关联的mapping_fields属性。
as_fields直接把关联的字段值映射成数据对象中的某个字段
这个特性是ONE_TO_ONE关联特有的,可以直接把关联数据映射到数据对象中,而不是作为一个关联数据。当关联数据的字段名和当前数据对象的字段名称有冲突时,还可以使用映射定义。
 
BELONGS_TO
Belongs_to关联表示当前模型从属于另外一个父对象,例如每个用户都属于一个部门。我们可以做如下关联定义。      
 ‘Dept’=> BELONGS_TO
完整方式定义为:
 ‘Dept’=> array( 
 ‘mapping_type’=>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’=> HAS_MANY
完整定义方式为:
 ‘Article’=> array( 
‘mapping_type’=>HAS_MANY,
                    ‘class_name’=>’Article’,
                    ‘foreign_key’=>’userId’,
                    ‘mapping_name’=>’articles’,
                    ‘mapping_order’=>’create_time desc’,
                    // 定义更多的关联属性
                             ……
                  ),
关联HAS_MANY定义支持的关联属性有:
class_name要关联的模型类名
mapping_name关联的映射名称,用于获取数据用
该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。
foreign_key关联的外键名称
外键的默认规则是当前数据对象名称_id,例如:
UserModel对应的可能是表think_user(注意:think只是一个表前缀,可以随意配置)
那么think_user表的外键默认为 user_id,如果不是,就必须在定义关联的时候定义 foreign_key。
parent_key自引用关联的关联字段
默认为parent_id
condition关联条件
关联查询的时候会自动带上外键的值,如果有额外的查询条件,可以通过定义关联的condition属性。
mapping_fields关联要查询的字段
默认情况下,关联查询的关联数据是关联表的全部字段,如果只是需要查询个别字段,可以定义关联的mapping_fields属性。
mapping_limit关联要返回的记录数目
mapping_order关联查询的排序
 
MANY_TO_MANY
MANY_TO_MANY关联表示当前模型可以属于多个对象,而父对象则可能包含有多个子对象,通常两者之间需要一个中间表类约束和关联。例如每个用户可以属于多个组,每个组可以有多个用户:
 ‘Group’=> MANY_TO_MANY
完整定义方式为:
        array(    ‘mapping_type’=>MANY_TO_MANY,
                    ‘class_name’=>’Group’,
                    ‘mapping_name’=>’groups’,
                    ‘foreign_key’=>’userId’,
                    ‘relation_foreign_key’=>’goupId’,
                    ‘relation_table’=>’think_gourpUser’)
 
MANY_TO_MANY支持的关联属性定义有:
class_name要关联的模型类名
mapping_name关联的映射名称,用于获取数据用
该名称不要和当前模型的字段有重复,否则会导致关联数据获取的冲突。
foreign_key关联的外键名称
外键的默认规则是当前数据对象名称_id,例如:
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属性。

***5.3.22.3关联查询
由于性能问题,新版取消了自动关联查询机制,而统一使用relation方法进行关联操作,relation方法不但可以启用关联还可以控制局部关联操作,实现了关联操作一切尽在掌握之中。
$User = D(“User”);
$user =    $User->realtion(true)->find(1);
输出$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’    =>HAS_ONE,
                    ‘class_name’  =>’Profile’,
‘foreign_key’=>’userId’,
‘as_fields’=>’email,nickname’,
                  ),
);
查询的结果就变成了下面的结果
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();
如果希望在完成的查询基础之上 再进行关联数据的查询,可以使用
$User = D(“User”);
$user = $User->find(1);
//表示对当前查询的数据对象进行关联数据获取
$profile = $User->relationGet(“Profile”);
事实上,除了当前的参考模型User外,其他的关联模型是不需要创建的。

***5.3.22.4关联操作
除了关联查询外,系统也支持关联数据的自动写入、更新和删除
关联写入
$User = D(“User”);
$data = array();
$data[“account”]     =      “ThinkPHP”;
$data[“password”]=      “123456”;
$data[“Profile”]        =       array(
        ’email’                =>’liu21st@gmail.com’,
‘nickname’        =>’流年’,
   );
$result = $User->realtion(true)->add($user);
这样就会自动写入关联的Profile数据。
同样,可以使用参数来控制要关联写入的数据:
$result = $User->realtion(“Profile”)->add($user);
 
关联更新
数据的关联更新和关联写入类似
$User = D(“User”);
$data[“account”]     =      “ThinkPHP”;
$data[“password”]=      “123456”;
$data[“Profile”]        =       array(
        ’email’                =>’liu21st@gmail.com’,
‘nickname’        =>’流年’,
   );
$result = $User-> relation(True)->where(‘id=3’)->save($data);
Realtion(true)会关联保存User模型定义的所有关联数据,如果只需要关联保存部分数据,可以使用:
$result = $User->relation(“Profile”)->save($data);
这样就只会同时更新关联的Profile数据。
关联保存的规则:
HAS_ONE关联数据的更新直接赋值
HAS_MANY的关联数据如果传入主键的值 则表示更新 否则就表示新增
MANY_TO_MANY的数据更新是删除之前的数据后重新写入
 
关联删除
删除用户ID为3的记录的同时删除关联数据
$result = $User->relation(true)->delete(“3″);
如果只需要关联删除部分数据,可以使用
$result = $User->relation(“Profile”)->delete(“3″);

***5.3.23动态模型
新版的模型可以在不同的类型之间切换,例如你可以从基本模型切换到高级模型或者视图模型,而当前的数据不会丢失,并可以控制要传递的参数和动态赋值。
要切换模型,可以使用:
$User = M(“User”); // 实例化User对象是基础模型类的实例
// 动态切换到高级模型类执行top10查询操作
$User->switchModel(“Adv”)->top10();
 
如果要传递参数,可以使用:
$User = D(“User”); // 实例化User对象是基础模型类的实例
// 动态切换到视图模型类并传入viewFields属性
$UserView = $User->switchModel(“View”,array(“viewFields”));
 
如果要动态赋值,可以使用:
$User = M(“User”); // 实例化User对象是基础模型类的实例
// 动态切换到高级模型类并传入data属性
$advUser = $User->switchModel(“Relation”);
// 或者在切换模型后再动态赋值给新的模型
$advUser->setProperty(“_link”,$link);
// 查找关联数据
$user = $advUser->relation(true)->find(1);

***5.4视图
***5.4.1模板定义
为了对模板文件更加有效的管理,ThinkPHP对模板文件进行目录划分,默认的模板文件定义规则是:
模板目录/模板主题/[分组名/]模块名/操作名+模板后缀
模板目录默认是项目下面的Tpl, 模板主题默认是default,模板主题功能是为了多模板切换而设计的,如果有多个模板主题的话,可以用DEFAULT_THEME参数设置默认的模板主题名。
在每个模板主题下面,是以项目的模块名为目录,然后是每个模块的具体操作模板文件,例如:
User模块的add操作 对应的模板文件就应该是:Tpl/default/User/add.html
模板文件的默认后缀的情况是.html,也可以通过TMPL_TEMPLATE_SUFFIX来配置成其他的。
如果项目启用了模块分组功能(假设User模块属于Home分组),那么默认对应的模板文件可能变成 :Tpl/default/Home/User/add.html
当然,分组功能也提供了 TMPL_FILE_DEPR参数来配置简化模板的目录层次。
例如 TMPL_FILE_DEPR如果配置成“_”的话,默认的模板文件就变成了:
Tpl/default/Home/User_add.html
正是因为系统有这样一种模板文件自动识别的规则,所以通常的display方法无需带任何参数即可输出对应的模板。

***5.4.2模板赋值
要在模板中输出变量,必须在在Action类中把变量传递给模板,视图类提供了 assign方法对模板变量赋值,无论何种变量类型都统一使用assign赋值。
$this->assign(‘name’,$value);
// 下面的写法是等效的
$this->name   = $value ;
系统只会输出设定的变量,其它变量不会输出,一定程度上保证了变量的安全性。
 
如果要同时输出多个模板变量,可以使用下面的方式:
$array = array();
$array[‘name’]    =    ‘thinkphp';
$array[’email’]    =    ‘liu21st@gmail.com’;
$array[‘phone’]    =    ‘12335678’;
$this->assign($array);
这样,就可以在模板文件中同时输出name、email和phone三个变量。
模板变量赋值后,怎么在模板文件中输出,需要根据选择的模板引擎来用不同的方法,如果使用的是内置的模板引擎,请参考后面的模板指南部分。如果你使用的是PHP本身作为模板引擎的话 ,就可以直接在模板文件里面输出了,如下:
<?php echo $name.'[‘.$email.’ ‘.$phone.’]';?>

***5.4.3模板输出
模板变量赋值后就需要调用模板文件来输出相关的变量,模板调用通过display方法来实现。我们在操作方法的最后使用:
$this->display();
根据前面的模板定义规则,因为系统会按照默认规则自动定位模板文件,所以通常display方法无需带任何参数即可输出对应的模板。这是模板输出的最简单的用法。
事情总有特例,或者根本不需要按模块进行分目录存放,不过display方法总是能够帮你解决问题。
Display方法提供了几种规则让你可以随心所欲的输出需要的模板,无论你的模板文件在什么位置。
下面来看具体的用法:
一、调用当前模块的其他操作模板
格式:display(‘操作名’)
例如,假设当前操作是User模块下面的read操作,我们需要调用User模块的edit操作模版,使用:
$this->display(‘edit’);
不需要写模板文件的路径和后缀。
 
二、调用其他模块的操作模板
格式:display(‘分组名:模块名:操作名’)其中分组名是可选的
例如,当前是User模块,我们需要调用Member模块的read操作模版 ,使用:
$this->display(‘Member:read’);
如果要调用分组Admin的Member模块的read操作模板,可以使用:
$this->display(‘Admin:Member:read’);
这种方式也不需要写模板文件的路径和后缀,严格来说,这里面的模块名和操作名并不一定需要有对应的模块或者操作,只是一个目录名称和文件名称而已,例如,你的项目里面可能根本没有Public模块,更没有Public模块的menu操作,但是一样可以使用
$this->display(‘Public:menu’);
输出这个模板文件。理解了这个,模板输出就清晰了。
 
三、调用其他主题的操作模板
格式:display(‘主题名@模块名:操作名’)
例如我们需要 调用Xp主题的User模块的edit操作模版,使用:
$this->display(‘Xp@User:edit’);
这种方式需要指定模块和操作名
 
四、直接全路径输出模板
格式:display(‘模板文件名’)
例如,我们直接输出当前的Public目录下面的menu.html模板文件,使用:
$this->display(‘./Public/menu.html’);
这种方式需要指定模板路径和后缀,这里的Public目录是位于当前项目入口文件位置下面。如果是其他的后缀文件,也支持直接输出,例如:
$this->display(‘./Public/menu.tpl’);
只要./Public/menu.tpl是一个实际存在的模板文件。如果使用的是相对路径的话,要注意当前位置是相对于项目的入口文件,而不是模板目录。
 
事实上,display方法还有其他的参数和用法。
有时候某个模板页面我们需要输出指定的编码,而不是默认的编码,可以使用:
$this->display(‘Member:read’, ‘gbk’);
或者输出的模板文件不是text/html格式的,而是XML格式的,可以用:
$this->display(‘Member:read’, ‘utf-8′, ‘text/xml’);

***5.4.4模板替换
在进行模板输出之前,系统还会对渲染的模板结果进行一些模板的特殊字符串替换操作,也就是实现了模板输出的替换和过滤。这个机制可以使得模板文件的定义更加方便,默认的替换规则有:
../Public: 会被替换成当前项目的公共模板目录 通常是 /项目目录/Tpl/default/Public/
__PUBLIC__:会被替换成当前网站的公共目录 通常是 /Public/
__TMPL__: 会替换成项目的模板目录 通常是 /项目目录/Tpl/default/
__ROOT__: 会替换成当前网站的地址(不含域名)
__APP__: 会替换成当前项目的URL地址 (不含域名)
__URL__: 会替换成当前模块的URL地址(不含域名)
__ACTION__:会替换成当前操作的URL地址 (不含域名)
__SELF__: 会替换成当前的页面URL
注意这些特殊的字符串是严格区别大小写的,并且这些特殊字符串的替换规则是可以更改或者增加的,我们只需要在项目配置文件中配置TMPL_PARSE_STRING就可以完成。如果有相同的数组索引,就会更改系统的默认规则。例如:
TMPL_PARSE_STRING  => array(
        ‘__PUBLIC__’ => ‘/Common’,  // 更改默认的__PUBLIC__替换规则
        ‘__UPLOAD__’ => ‘/Public/Uploads/’,  // 增加新的上传路径替换规则
)
事实上,表单令牌验证的令牌验证字符串的自动生成,也是在这个阶段进行替换的。

***5.4.5获取内容
有些时候我们不想直接输出模板内容,而是希望对内容再进行一些处理后输出,就可以使用fetch方法来获取解析后的模板内容,在Action类里面使用:
$content = $this->fetch();
fetch的参数用法和Display方法基本一致,也可以使用:
$content = $this->fetch(‘Member:read’);
区别就在于display方法直接输出模板文件渲染后的内容,而fetch方法是返回模板文件渲染后的内容。如何对返回的结果content进行处理,完全由开发人员自行决定了。这是模板替换的另外一种高级方式,比较灵活,而且不需要通过配置的方式。
注意,fetch方法仍然会执行上面的模板替换操作。

***5.4.6布局模板
新版的ThinkPHP可以自动识别模板文件中的布局模板,不再需要专门使用layout方法进行布局模板的输出了。布局模板本身的用法和普通的模板一样,只是增加了一个布局标签的用法,并且布局模板可以用于任何模板引擎,都可以很好的支持。
我们可以在布局模板里面使用下面的格式定义布局:
<!– layout::模板文件规则::缓存时间(秒) –>
这里的模板文件规则和display的参数用法是一致的,详细的可以参考模板输出部分的内容。
例如:
<!– layout::Public:header::60 –>
<!– layout::Public:content::30 –>
<!– layout::Public:footer::60 –>
如果使用的是内置的模板引擎的话,还可以使用下面的布局标签来定义,效果一致:
<layout name=”Public:header” cache=”60″ />
<layout name=”Public:content” cache=”30″ />
<layout name=”Public:footer” cache=”60″ />
三个布局定义(标签)分别导入了三个模板文件,由于Include标签导入的外部文件无法检测模板更新,而布局模板恰好可以很好的解决这个问题。
假设上面的布局模板文件名称为default.html位于Index目录下面,那么我们就可以在Action里面调用输出:
$this->display(‘Index:default’);
通常来说,我们可以不用重复定义很多的布局模板,使用动态布局模板来简化布局模板的定义。例如,我们把上面的布局模板修改为:
<!– layout::Public:header::60 –>
<!– layout::$content::30 –>
<!– layout::Public:footer::60 –>
或者采用内置模板引擎的layout标签定义:
<layout name=”Public:header” cache=”60″ />
<layout name=”$content” cache=”30″ />
<layout name=”Public:footer” cache=”60″ />
这样所有的模板都会调用Public/header.html头部和Public/footer.html尾部,而中间的内容是通过变量动态控制输出调用的模板。
$this->assign(‘content’, ‘User:list’);
$this->display(‘Index:default’);
布局模板只是为了方便视图的定义,布局模板本身并不调用模板对应的操作方法,也就是说布局模板中的变量赋值仍然需要在控制器中进行。

***5.4.7系统模板
系统有一些内置的模板文件用于异常页面和页面Trace功能的输出,你可以定制这些模板页面,满足自己的需要。默认的系统模板主要有:
页面Trace模板:默认位于系统目录的Tpl/PageTrace.tpl.php是一个php文件,可更改TMPL_TRACE_FILE进行配置。
异常模板:默认位于系统目录的Tpl/ThinkException.tpl.php,可以更改TMPL_EXCEPTION_FILE进行配置。
以上两个系统模板都采用php原生语法定义,不支持模板标签。

***5.4.8静态生成
ThinkPHP提供了灵活的静态文件生成功能,可以在输出模板的同时生成需要的静态文件,以供调用。
在Action中使用buildHtml方法即可创建静态文件,buildHtml方法的第一个参数就要生成的静态文件名,后面的参数和display方法一致,内部其实是调用了前面提到的fetch方法获取模板输出然后创建静态文件。用法如下:
$this->buildHtml(‘静态文件’, ‘静态路径’,’模板文件’);
静态路径如果留空的话 默认保存在HTML_PATH(默认的HTML_PATH路径位于项目目录下面的Html目录,如果没有的话手动创建)定义的路径下面,,静态文件可以随意设置,也可以包括路径,如果不存在的路径系统会自动创建,例如:
$this->buildHtml(“Member/{$id}”,”,’Member:read’);
上面的用法表示获取Member模块的read操作模板输出内容后,根据用户的编号生成一个静态文件。位于HTML_PATH下面的Member/1.html,如果Member子目录不存在,系统会自动创建。

***5.4.9模板引擎
系统支持原生的PHP模板,而且本身内置了一个基于XML的高效的编译型模板引擎,无论在功能还是性能方面都优秀过Smarty。系统默认使用的模板引擎是内置模板引擎,关于这个模板引擎的标签详细使用可以参考模板指南部分。
内置的模板引擎也可以直接支持在模板文件中采用PHP原生代码和模板标签的混合使用,如果需要完全使用PHP本身作为模板引擎,可以配置:
‘TMPL_ENGINE_TYPE’ => ‘PHP’
可以达到最佳的效率。

***5.4.10使用第三方模板引擎
系统支持模板引擎的扩展机制,并且官方提供了包括Smarty、EaseTemplate、TemplateLite和Smart在内的第三方模板引擎扩展。我们以Smarty模板引擎为例,来说明下如何使用第三方模板引擎。
首先,需要下载官方的模板引擎扩展,并放到系统目录的Lib\Think\Util\Template目录下面,然后,下载最新的Smarty模板引擎文件放到系统目录的Vendor第三方类库目录。
剩下的,我们要做的只是简单的配置下模板引擎名称即可,例如在项目配置文件里面设置:
‘TMPL_ENGINE_TYPE’ => ‘Smarty’
就可以用smarty标签来定义你的模板文件了,并且在模板文件的赋值和输出上面,和原来的方式一样,例如我们在上面提到的用assign赋值模板变量、display和fetch方法的使用、模板文件的定位规则、模板替换功能仍然都可以使用。
对于某些第三方的模板引擎,还可以用TMPL_ENGINE_CONFIG参数进行自定义的配置。
例如对于Smarty模板引擎而言,我们可以进行下面的配置参数定义:
‘TMPL_ENGINE_CONFIG’ => array(
             ‘caching’ => true,
             ‘template_dir’ => TMPL_PATH,
             ‘cache_dir’ => TEMP_PATH,
)
***5.5错误和日志

***5.5.1异常处理
和PHP默认的异常处理不同,ThinkPHP抛出的不是单纯的错误信息,而是一个人性化的错误页面。

一旦系统发生严重错误会自动抛出异常,也可以用ThinkPHP定义的throw_exception方法手动抛出异常。
throw_exception方法支持三个参数:
$msg异常信息,必须
$type异常类型,即异常类的名称,默认是系统异常基础类ThinkException
$code异常代码默认为0
如果指定的异常类型不存在,系统自动调用halt方法直接输出异常信息文字,而不输出异常详细信息。
下面是throw_exception函数的一些使用例子:
throw_exception(‘新增失败’);
throw_exception(‘信息录入错误’,’InfoException’);
同样也可以使用throw关键字来抛出异常,下面的写法是等效的:
throw new ThinkException(‘新增失败’);
throw new InfoException(‘信息录入错误’);
如果需要,我们建议在项目的类库目录下面增加Exception目录用于专门存放异常类库,以更加精确地定位异常。
异常页面的模板是可以修改的,如果没有定义,则采用系统内置的异常模板文件,该模板文件位于系统目录下目的Tpl目录下面的 ThinkException.tpl.php文件。通过设置EXCEPTION_TMPL_FILE配置参数来修改系统默认的异常模板文件,例如:
‘EXCEPTION_TMPL_FILE’ => APP_PATH.’/Public/exception.php’
异常模板中可以使用的异常变量有:
$e[‘file’]异常文件名
$e[‘line’]异常发生的文件行数
$e[‘message’]异常信息
$e[‘trace’]异常的详细Trace信息
因为异常模板使用的是原生PHP代码,所以还可以支持任何的PHP方法和系统变量使用。
 
抛出异常后通常会显示具体的错误信息,如果不想让用户看到具体的错误信息,可以设置关闭错误信息的显示并设置统一的错误提示信息,例如:
‘SHOW_ERROR_MSG’ =>false,
‘ERROR_MESSAGE’ => ‘发生错误!’
设置之后,所有的异常页面只会显示“发生错误!”这样的提示信息,但是日志文件中仍然可以查看具体的错误信息。
另外一种方式是配置ERROR_PAGE参数,把所有异常和错误都指向一个统一页面,从而避免让用户看到异常信息,通常在部署模式下面使用。ERROR_PAGE参数必须是一个完整的URL地址,例如:
‘ERROR_PAGE’ => ‘/Public/error.html’
如果不在当前域名,还可以指定域名:
‘ERROR_PAGE’ => ‘http://www.myDomain.com/Public/error.html’
注意ERROR_PAGE所指向的页面不能再使用异常的模板变量了。

***5.5.2日志处理
日志的处理工作是由系统自动进行的,在开启日志记录的情况下,会记录下允许的日志级别的所有日志信息。其中,SQL日志级别必须在调试模式开启下有效,否则就不会记录。
系统的日志记录由核心的Log类完成,提供了多种方式记录了不同的级别的日志信息。

***5.5.3日志级别
ThinkPHP对系统的日志按照级别来分类,包括:
EMERG: 严重错误,导致系统崩溃无法使用
ALERT: 警戒性错误, 必须被立即修改的错误
 CRIT: 临界值错误, 超过临界值的错误,例如一天24小时,而输入的是25小时这样
 ERR: 一般性错误
 WARN: 警告性错误, 需要发出警告的错误
 NOTICE:通知,程序可以运行但是还不够完美的错误
 INFO:信息,程序输出信息
DEBUG: 调试,用于调试信息
 SQL:SQL语句,该级别只在调试模式开启时有效
 
要开启日志记录,必须在配置中开启LOG_RECORD参数
我们可以在项目配置文件中配置需要记录的日志级别,例如:
‘LOG_RECORD’ => true, //开启日志记录
‘LOG_RECORD_LEVEL’     =>   array(‘EMERG’,’ALERT’,’CRIT’,’ERR’),
只是记录EMERG ALERT CRIT ERR错误。

***5.5.4记录方式
日志的记录方式包括下面四种方式:
SYSTEM:日志发送到PHP的系统日志记录
MAIL:日志通过邮件方式发送
 TCP:日志通过TCP方式发送
 FILE:日志通过文件方式记录(默认方式)
 
FILE方式
默认采用文件方式记录日志信息,文件的格式是:年(简写)_月_日.log,例如:
09_10_01.log  表示2009年10月1日的日志文件
可以设置LOG_FILE_SIZE参数来限制日志文件的大小,超过大小的日志会形成备份文件。备份文件的格式是在当前文件名前面加上备份的时间戳,例如:
1189571417-07_09_12.log备份的日志文件
日志文件的内容格式为:
[时间 ]日志级别:日志信息
其中的时间显示可以动态配置,默认是采用 [ c ],例如我们可以改成:
Log::$format = ‘[ Y-m-d H:i:s ]';
其格式定义和date函数的用法一致
默认情况下具体的日志信息类似于下面的内容:
[ 2009-08-25T18:09:22+08:00 ] NOTIC: [8] Undefined variable: verify PublicAction.class.php第 162行.
[ 2009-08-25T18:09:24+08:00 ] SQL:  RunTime:0.214238s SQL = SHOW COLUMNS FROM think_user
[ 2009-08-25T18:09:24+08:00 ] SQL:  RunTime:0.039159s SQL = SELECT * FROM `think_user` WHERE ( `account` = ‘admin’ ) AND ( `status` > 0 ) LIMIT 1
其他的日志类型的详细资料可以参考PHP手册中关于error_log方法的使用。

***5.5.5手动记录
通常日志文件的写入是自动完成的,如果我们需要在开发的过程中手动记录日志信息,可以使用Log类的方法来操作。日志文件的写入有两种方法:
一、使用Log::Write($message,$level,$type,$file)
$message是要记录的日志信息
$level日志级别
$type日志类型
$file日志文件位置和名称,该参数可以改变系统默认的日志文件命名。
Write方法把日志信息直接写入相关的日志文件里面。
Log::write(‘调试的SQL:’.$SQL, Log::SQL);
 
二、使用Log::record和 Log::save方法
Log::record($message,$level,$type);
其参数含义和write方法一致,不过record方法只是把日志信息保存到内存,并没有真正写入日志文件。直到调用Log::save方法。
 
Log::save()
保存Log::record方法记录的日志信息到日志文件。
 
例如:
Log::record(‘测试调试错误信息’, Log::DEBUG);
Log::record(‘调试的SQL:’.$SQL, Log::SQL);
Log::save();

***5.6调试

***5.6.1调试模式
在开启了调试模式之后,我们会看到更加详细的错误信息,调试模式的作用在于显示或者记录了更多的日志信息,以便我们在项目开发过程中快速定位和解决问题。
开启调试模式很简单,只要在项目配置文件里面设置
‘APP_DEBUG’ => true,
开启调试模式之后,系统在运行的时候首先会检查项目是否有定义调试配置文件,如果没有定义则调用框架默认的调试配置文件里面的参数,这些是系统为调试模式预设的默认配置。系统的默认调试配置文件位于ThinkPHP\Common\debug.php。在这个默认的调试配置文件里面,系统开启了日志记录、关闭了页面防刷新机制、关闭了模板缓存,记录了执行过程中的SQL语句和运行时间,并且开启了页面运行时间显示和Trace功能。如果你觉得默认的调试配置不符合你的项目调试需要,你还可以在项目里面定义调试配置文件。
调试模式下面不会生成项目编译缓存,但是仍然会生成核心编译缓存,如果不希望生成核心缓存文件的话,可以在项目入口文件里面设置NO_CACHE_RUNTIME,例如:
define(‘NO_CACHE_RUNTIME’,True);
以及设置对编译缓存的内容是否进行去空白和注释,例如:
define(‘STRIP_RUNTIME_SPACE’,false);
则生成的编译缓存文件是没有经过去注释和空白的,仅仅是把文件合并到一起,这样的好处是便于调试的错误定位,建议部署模式的时候把上面的设置为True或者删除该定义。

***5.6.2调试配置
我们可以给项目单独定义调试配置文件,用于项目在调试模式下面的配置信息。
项目的调试配置文件位于配置目录Conf目录下面,文件名定义为debug.php,格式和项目配置文件的定义方法完全相同。
调试配置文件仅仅在启用调试模式的情况下有效,一旦项目关闭调试模式,就依然会使用项目配置文件里面的配置信息。
需要注意的是,调试模式下面并不是说项目只会加载调试配置文件,项目配置文件依然会首先加载的,只不过调试配置文件里面存在和项目配置文件有冲突的情况下,会覆盖项目配置文件里面的相同参数。也就是说,和项目配置文件相同的参数可以不必在调试模式下面进行定义。在下面的情况下,你通常会考虑使用项目的调试配置文件:
 调试模式需要连接不同的测试数据库
需要增加额外的调试配置信息

***5.6.3运行状态
开启调试模式后,默认会显示当前页面的运行状态,这是一个包括了运行时间、内存开销、数据库读写次数和缓存读写次数的详细运行数据。显示结果信息类似于下面:
Process: 0.085s ( Load:0.001s Init:0.005s Exec:0.025s Template:0.054s ) | DB :2 queries 0 writes | Cache :1 gets 0 writes | UseMem:471 kb
最前面是整体的执行时间,中间是详细的阶段执行时间,然后是数据库读写次数和缓存读写次数显示,最后则是内存开销显示。如果当前页面没有任何数据库操作或者缓存操作的话,是不会显示相关信息的。内存开销的显示需要服务器开启memory_get_usage方法支持。
如果在非调式模式下面,其实我们也可以开启这样的运行状态显示。只需要在项目配置文件中开启相关的配置参数,如下:
 ‘SHOW_RUN_TIME’=>true,          //运行时间显示
 ‘SHOW_ADV_TIME’=>true,          //显示详细的运行时间
 ‘SHOW_DB_TIMES’=>true,          //显示数据库查询和写入次数
 ‘SHOW_CACHE_TIMES’=>true,       //显示缓存操作次数
 ‘SHOW_USE_MEM’=>true,           //显示内存开销
上面的每项参数都可以单独开启,例如,你只需要显示整体的运行时间,而不关心详细的阶段运行时间,可以关闭详细运行时间显示:
‘SHOW_ADV_TIME’=> false,          //关闭详细的运行时间

***5.6.4页面Trace
页面Trace功能是ThinkPHP的一个用于开发调试的辅助手段。可以实时显示当前页面的操作的请求信息、运行情况、SQL执行、错误提示等,启用调试模式的话,页面Trace功能会默认开启(除非在项目的调试配置文件中关闭),并且系统默认的Trace信息包括:当前页面、请求方法、通信协议、请求时间、用户代理、会话ID、运行情况、SQL记录、错误记录和文件加载情况。默认的页面Trace的显示如图所示:
 
Trace页面定制
页面Trace信息的显示模板是可以定制的,默认位于系统目录的Tpl/PageTrace.tpl.php是一个php文件,可以根据项目自身的需要定制,更改TMPL_TRACE_FILE进行配置即可。
例如:
‘TMPL_TRACE_FILE’ => APP_PATH.’/Public/trace.php’
关键的输出代码是:
<?php foreach ($_trace as $key=>$info){
echo $key.’ : ‘.$info.'<br/>';
}?>
 
Trace信息定制
如果需要扩展自己的Trace信息,有下面几种方式:
第一种方式:在当前项目的配置目录下面定义 trace.php文件,返回数组方式的定义,例如:
return array(
‘当前页面’=>$_SERVER[‘PHP_SELF’],
‘通信协议’=>$_SERVER[‘SERVER_PROTOCOL’],…
);
在显示页面Trace信息的时候会把这个部分定义的信息追加到系统默认的信息之后,这种方式通常用于Trace项目的公共信息。
 
第二种方式:在Action方法里面使用trace方法来增加Trace信息,该部分可以用于系统的开发阶段调试。例如:
$this->trace(‘执行时间’,$runTime);
$this->trace(‘Name的值’,$name);
$this->trace(‘GET变量’,dump($_GET,false));

***5.6.5调试方法
调试模式并不能完全满足我们调试的需要,有时候我们需要手动的输出一些调试信息。
除了本身可以借助一些开发工具进行调试外,ThinkPHP还提供了一些内置的调试函数和类库。
输出某个变量是开发过程中经常会用到的调试方法,除了使用php内置的var_dump和print_r之外,ThinkPHP框架内置了一个对浏览器友好的var_dump方法,用于输出变量的信息到浏览器查看。
dump($var, $echo=true, $label=null)  //输出变量信息
例如:
$Blog = D(“Blog”);
$blog = $Blog->find(3);
dump($blog);
//在浏览器输出的结果是
array(12) {
  [“id”] => string(1) “3”
  [“name”] => string(0) “”
  [“userId”] => string(1) “0”
  [“categoryId”] => string(1) “0”
  [“title”] => string(4) “test”
  [“content”] => string(4) “test”
  [“cTime”] => string(1) “0”
  [“mTime”] => string(1) “0”
  [“status”] => string(1) “0”
  [“readCount”] => string(1) “0”
  [“commentCount”] => string(1) “0”
  [“tags”] => string(0) “”
}
 
使用下面的方法可以很方便的获取某个区间的运行时间和内存占用情况
debug_start($label=”) //记录调试开始时间
debug_end($label=”)  //输出调试范围运行时间(相同label属于一个调试范围)
例如:
debug_start(‘run’);
$blog = D(“Blog”);
$blog->select();
debug_end(‘run’);
会输出下面的运行信息:
Process run: Times 0.007730s Memories 76 k
如果要输出内存占用情况,需要服务器支持memory_get_usage方法
 
我们可以使用下面的方法输出错误信息:
halt($msg)  //输出错误信息,并中止执行

***5.6.6模型调试
在模型操作中 ,为了更好的查明错误,经常需要查看下最近使用的SQL语句,我们可以用getLastsql方法来输出上次执行的sql语句。例如:
$User = M(“User”); // 实例化User对象
$User->find(1);
echo $User->getLastSql();
输出结果是
SELECT * FROM think_user  WHERE id = 1

***5.6.7调试类
更高级的调试方法是使用Debug类
Debug::mark($name); //标记一个调试位置
Debug::useTime($start,$end); //返回区间所用的时间
Debug::useMemory($start,$end); //返回区间所用的内存

***5.7缓存
***5.7.1缓存方式
ThinkPHP在数据缓存方面包括文件方式、共享内存方式和数据库方式在内的多种方式进行缓存,通过插件方式还可以增加以后需要的缓存类,让应用开发可以选择更加适合自己的缓存方式,从而有效地提高应用执行效率。目前已经支持的缓存方式包括:File、Apachenote、Apc、Eaccelerator、Memcache、Shmop、Sqlite、Db和Xcache。

***5.7.2缓存类
所有的缓存方式都被统一使用公共的调用接口,这个接口就是Cache缓存类。
缓存类的使用很简单:
$Cache = Cache::getInstance(‘缓存方式’,’缓存参数’);
例如,使用Xcache作为缓存方式,缓存有效期60秒。
$Cache = Cache::getInstance(‘Xcache’,array(‘expire’=>’60’));
存取缓存数据
$Cache->set(‘name’,’ThinkPHP’);  // 缓存name数据
$value = $Cache->get(‘name’);  // 获取缓存的name数据
$Cache->rm(‘name’);  // 删除缓存的name数据
或者使用下面的方法是等效的:
$Cache->name = ‘ThinkPHP';
$value = $Cache->name;
Unset($Cache->name);

***5.7.2动态缓存
为了进一步简化缓存存取操作,ThinkPHP把所有的缓存机制统一成一个S方法来进行操作,所以在使用不同的缓存方式的时候并不需要关注具体的缓存细节。例如:
// 使用data标识缓存$Data数据
S(‘data’,$Data);
// 缓存$Data数据3600秒
S(‘data’,$Data,3600);
// 获取缓存数据
$Data = S(‘data’);
// 删除缓存数据
S(‘data’,NULL);
系统默认的缓存方式是采用File方式缓存,我们可以在项目配置文件里面定义其他的缓存方式,例如,修改默认的缓存方式为Xcache(当然,你的环境需要支持Xcache)
‘DATA_CACHE_TYPE’=>’Xcache’
通过上面的定义,相同的代码就会使用Xcache方式来缓存了,而事实上,代码并没有任何改变。
当然,我们还可以在S方法里面显式的指定缓存方式,例如
S(‘data’,$Data,3600,’File’);
// 或者动态切换缓存方式
C(‘DATA_CACHE_TYPE’,’Xcache’);
S(‘data’,$Data,3600);
$data = S(‘data’);
// 操作完成后切换会默认的缓存方式
C(‘DATA_CACHE_TYPE’,’File’);
对于File方式缓存下的缓存目录下面因为缓存数据过多而导致存在大量的文件问题,ThinkPHP也给出了解决方案,可以启用哈希子目录缓存的方式,只需要设置
‘DATA_CACHE_SUBDIR’=>true
还可以设置哈希目录的层次,例如:
‘DATA_PATH_LEVEL’=>2
就可以根据缓存标识的哈希自动创建多层子目录来缓存。

***5.7.4快速缓存
S方法支持缓存有效期,在很多情况下,可能我们并不需要有效期的概念,或者使用文件方式的缓存就能够满足要求,所以系统还提供了一个专门用于文件方式的快速缓存方法F方法。F方法只能用于缓存简单数据类型,不支持有效期和缓存对象,使用如下:
快速缓存Data数据,默认保存在DATA_PATH目录下面
F(‘data’,$Data);
快速缓存Data数据,保存到指定的目录
F(‘data’,$Data,TEMP_PATH);
获取缓存数据
$Data = F(‘data’);
删除缓存数据
F(‘data’,NULL);
F方法支持自动创建缓存子目录,例如:
在DATA_PATH目录下面缓存data数据,如果User子目录不存在,则自动创建
F(‘User/data’,$Data);
系统内置的数据字段信息缓存就是用了快速缓存机制。

***5.7.5静态缓存
ThinkPHP内置了静态缓存的功能,并且支持静态缓存的规则定义。
要使用静态缓存功能,需要开启HTML_CACHE_ON参数,并且在项目配置目录下面增加静态缓存规则文件 htmls.php,两者缺一不可。否则静态缓存不会生效。
静态规则文件的定义方式如下:
return array(
‘ActionName’=>array(‘静态规则’, ‘静态缓存有效期’, ‘附加规则’),
‘ModuleName’=>array(‘静态规则’, ‘静态缓存有效期’, ‘附加规则’),
‘ModuleName:ActionName’=>array(‘静态规则’, ‘静态缓存有效期’, ‘附加规则’),
‘*’=>array(‘静态规则’, ‘静态缓存有效期’, ‘附加规则’),
…更多操作的静态规则
)
静态缓存文件的根目录在HTML_PATH定义的路径下面,并且只有定义了静态规则的操作才会进行静态缓存,注意,静态规则的定义有三种方式,
第一种是定义全局的操作静态规则,例如定义所有的read操作的静态规则为
         ‘read’=>array(‘{id}’,’60’)
        其中,{id}表示取$_GET[‘id’]为静态缓存文件名,第二个参数表示缓存60秒
第二种是定义全局的模块静态规则,例如定义所有的User模块的静态规则为
         ‘User:’=>array(‘User/{:action}_{id}’,’600′)
        其中,{:action}表示当前的操作名称
第三种是定义某个模块的操作的静态规则,例如,我们需要定义Blog模块的read操作进行静态缓存
‘Blog:read’=>array(‘{id}’,-1)
有个别特殊的规则,例如空模块和空操作的静态规则的定义,可以使用下面的方式:
‘Empty:index’=>array(‘{:module}_{:action}’,-1)  // 定义空模块的静态规则
‘User:_empty’=>array(‘User/{:action}’,-1)  // 定义空操作的静态规则
第四种方式是定义全局的静态缓存规则,这个属于特殊情况下的使用,任何模块的操作都适用,例如
‘*’=>array(‘{$_SERVER.REQUEST_URI|md5}’),根据当前的URL进行缓存
 
静态规则的写法可以包括以下情况
1、使用系统变量 包括 _GET _REQUEST _SERVER _SESSION _COOKIE
格式:{$_×××|function}
例如:{$_GET.name} {$_SERVER. REQUEST_URI}
 
2、使用框架特定的变量
例如:{:app}、{:group}、{:module}和{:action}分别表示当前项目名、分组名、模块名和操作名
 
3、使用_GET变量
{var|function}
也就是说 {id}其实等效于 {$_GET.id}
 
4、直接使用函数
{|function}   
例如:{|time}
 
5、支持混合定义,例如我们可以定义一个静态规则为:
‘{id},{name|md5}’
在{}之外的字符作为字符串对待,如果包含有”/”,会自动创建目录。
例如,定义下面的静态规则:
{:module}/{:action}_{id}
则会在静态目录下面创建模块名称的子目录,然后写入操作名_id.shtml文件。
 
静态有效时间单位为秒如果不定义,则会获取配置参数HTML_CACHE_TIME的设置值
附加规则通常用于对静态规则进行函数运算,例如
‘read’=>array(‘Think{id},{name}’,’60’, ‘md5′)
翻译后的静态规则是 md5(‘Think’.$_GET[‘id’]. ‘, ‘.$_GET[‘name’]);
和静态缓存相关的配置参数包括:
HTML_CACHE_ON是否开启静态缓存功能
HTML_FILE_SUFFIX静态文件后缀 惯例配置的值是 .shtml
HTML_CACHE_TIME默认的静态缓存有效期 默认60秒 可以在静态规则定义覆盖
HTML_READ_TYPE页面静态化后读取的规则
一种是直接读取缓存文件输出(readfile方式HTML_READ_TYPE为0) 这是系统默认的方式,属于隐含静态化,用户看到的URL地址是没有变化的。
另外一种方式是重定向到静态文件的方式(HTML_READ_TYPE为1),这种方式下面,用户可以看到URL的地址属于静态页面地址,比较直观。

***5.8安全

***5.8.1防止SQL注入
对于WEB应用来说,SQL注入攻击无疑是首要防范的安全问题,系统底层对于数据安全方面本身进行了很多的处理和相应的防范机制,例如:
$User = M(“User”); // 实例化User对象
$User->find($_GET[“id”]);
即便用户输入了一些恶意的id参数,系统也会自动加上引号避免恶意注入。事实上,ThinkPHP对所有的数据库CURD的数据都会进行escape_string处理。
通常的安全隐患在于你的查询条件使用了字符串参数,然后其中一些变量又依赖由客户端的用户输入,要有效的防止SQL注入问题,我们建议:
 查询条件尽量使用数组方式,这是更为安全的方式;
 开启数据字段类型验证,可以对数值数据类型做强制转换;
 使用自动验证和自动完成机制进行针对应用的自定义过滤;
字段类型检查、自动验证和自动完成机制我们在模型部分已经有详细的描述,

***5.8.2输入过滤
永远不要相信客户端提交的数据,所以对于输入数据的过滤势在必行,我们建议:
 开启令牌验证避免数据的远程提交;
 使用自动验证和自动完成机制进行初步过滤;
对用户输入的数据进行有效(根据你的应用)的过滤,常见的安全过滤函数包括stripslashes、htmlentities、htmlspecialchars等,官方的扩展类库中的ORG.Util.Input类则提供了更好的解决方法;

***5.8.3防止XSS攻击
XSS(跨站脚本攻击)可以用于窃取其他用户的Cookie信息,要避免此类问题,可以采用如下解决方案:
 直接过滤所有的JavaScript脚本;
 转义Html元字符,使用htmlentities、htmlspecialchars等函数;
系统的扩展函数库提供了XSS安全过滤的remove_xss方法;

***5.8.4其他安全建议
下面的一些安全建议也是非常重要的:
 对所有公共的操作方法做必要的安全检查,防止用户通过URL直接调用;
 不要缓存需要用户认证的页面;
 对用户的上传文件,做必要的安全检查,例如上传路径和非法格式,官方的扩展类库中的ORG.Net.UploadFile类提供了上传类的安全解决方案。
 如非必要,不要开启服务器的目录浏览权限;
对于项目进行充分的测试,不要生成业务逻辑的安全隐患(这可能是最大的安全问题);

***5.8.5目录安全文件
对于某些服务器开启了目录浏览权限的话,用户就可以直接在浏览器输入URL地址查看目录了。系统内建了目录安全文件机制,可以有效的解决此类问题。
如果在入口文件里面定义了BUILD_DIR_SECURE常量为True,还会自动给项目目录生成目录安全文件(在相关的目录下面生成空白的htm文件),并且可以自定义安全文件的文件名 DIR_SECURE_FILENAME,默认是index.html,如果你想给你们的安全文件定义为default.html可以使用
define(‘DIR_SECURE_FILENAME’, ‘default.html’);
还可以支持多个安全文件写入,例如你想同时写入index.html和index.htm两个文件,以满足不同的服务器部署环境,可以这样定义:
define(‘DIR_SECURE_FILENAME’, ‘index.html,index.htm’);
默认的安全文件只是写入一个空白字符串,如果需要写入其他内容,可以通过DIR_SECURE_CONTENT参数来指定,例如:
define(‘DIR_SECURE_CONTENT’, ‘deney Access!’);
下面是一个完整的使用目录安全写入的例子
define(‘BUILD_DIR_SECURE’,true);
define(‘DIR_SECURE_FILENAME’, ‘default.html’);
define(‘DIR_SECURE_CONTENT’, ‘deney Access!’);

***5.8.6保护模板文件
因为模板文件中可能会泄露数据表的字段信息,有两种方法可以保护你的模板文件不被访问到:
第一种方式是配置.htaccess文件,针对Apache服务器而言。
把以下代码保存在项目的模板目录目录(默认是Tpl)下保存存为.htaccess。
<Files *.html>
Order Allow,Deny
Deny from all
</Files>
如果你的模板文件后缀不是html可以将*.html改成你的模板文件的后缀。
 
第二种方式是针对独立的服务器,不适合虚拟主机用户。
按照我们之前提过的网站安全部署方案,把项目目录部署到网站WEB目录之外,这样,整个项目目录都不能直接访问,当然模板文件也保护起来了。

***5.9部署
***5.9.1部署优化
在部署阶段,请关闭调试模式,并且注意下面事项,进行尽可能的性能优化:
 如果非必要,请在项目配置中关闭任何日志写入;
 开启模板缓存,并设置有效期为-1;
 启动ALLINONE模式(后面会讲到);
对于实时性要求不高的动态数据进行缓存处理;

***5.9.2.ALLINONE模式
ALLINONE模式指的是ThinkPHP可以把核心编译缓存和项目编译缓存合并到一个文件里面去,并且过滤掉一些运行模式不需要执行的代码,并且对于用户的自定义常量全部统一定义,不再进行额外的检测。ALLINONE模式一般是在开发调试完成之后,希望进一步提高系统的整体性能的时候开启。开启ALLINONE模式只需要在入口文件中添加定义:
define(‘RUNTIME_ALLINONE’, true);  //开启ALLINONE运行模式
开启ALLINONE运行模式后需要清空系统原来的编译缓存文件,第一次运行的时候系统会自动生成一个~allinone.php的缓存文件,第二次就会直接读取缓存文件而跳过一些不必要的初始化过程。~allinone.php编译缓存文件不是简单的~runtime.php和~app.php的合并,剔除了一些运行模式过程中不需要的方法和代码。
需要注意的是,在ALLINONE模式下面,即使调试模式开启也是无效的。系统不支持对ALLINONE运行模式的开发调试功能。因此,大多数情况用于生产部署环境。

***5.10杂项
***5.10.1多语言
ThinkPHP内置多语言支持,如果你的应用涉及到国际化的支持,那么可以定义相关的语言包文件。任何字符串形式的输出,都可以定义语言常量。可以为项目定义不同的语言文件,框架的系统语言包目录在系统框架的Lang目录下面,每个语言都对应一个语言包文件,系统默认只有简体中文语言包文件zh-cn.php,如果要增加繁体中文 zh-tw或者英文en,只要增加相应的文件。
语言包的使用由系统自动判断当前用户的浏览器支持语言来定位,如果找不到相关的语言包文件,会使用默认的语言。如果浏览器支持多种语言,那么取第一种支持语言。
ThinkPHP的多语言支持已经相当完善了,可以满足应用的多语言需求。这里是指的是模板多语言支持,数据的多语言转换(翻译)不在这个范畴之内。ThinkPHP具备语言包定义、自动识别、动态定义语言参数的功能。并且可以自动识别用户浏览器的语言,从而选择相应的语言包(如果有定义)。例如:
throw_exception(’新增用户失败!’);
我们在语言包里面增加了ADD_USER_ERROR语言配置变量的话,在程序中的写法就要改为:
throw_exception(L(‘ADD_USER_ERROR’));
也就是说,字符串信息要改成L方法和语言定义来表示。
项目语言包文件位于项目的Lang目录下面,并且按照语言类别分子目录存放,在执行的时候系统会自动加载,无需手动加载。语言包文件可以按照模块来定义,每个模块单独定义语言包文件,文件名和模块名称相同,例如:
Lang/zh-cn/user.php表示给User模块定义简体中文语言包文件
Lang/zh-tw/user.php表示给User模块定义繁体中文语言包文件
语言子目录采用浏览器的语言命名(全部小写)定义,例如English (United States) 可以使用en-us作为目录名。如果项目比较小,整个项目只有一个语言包文件,那可以定义common.php文件,而无需按照模块分开定义。
 
语言文件定义
ThinkPHP语言文件定义采用返回数组方式:
return array(
‘lan_define’=>’欢迎使用ThinkPHP’,
);
 
要在程序里面设置语言定义的值,使用下面的方式:
L(‘define2′,’语言定义’);
$value = L(‘define2′);
 
上面的语言包是指项目的语言包,如果在提示信息的时候使用了框架底层的提示,那么还需要定义系统的语言包,系统语言包目录位于ThinkPHP目录下面的Lang目录。
通常多语言的使用是在Action控制器里面,但是模型类的自动验证功能里面会用到提示信息,这个部分也可以使用多语言的特性。例如:
原来的方式是把提示信息直接写在模型里面定义
array(‘title’,’require’,’标题必须!’,1),
如果使用了多语言功能的话(假设,我们在当前语言包里面定义了’ lang_var’=>’标题必须!’)
还可以这样定义模型的自动验证
array(‘title’,’require’,'{%lang_var}’,1),
 
如果要在模板中输出语言变量不需要在Action中赋值,可以直接使用模板引擎特殊标签来直接输出语言定义的值:
{$Think.lang.lang_var}
可以输出当前选择的语言包里面定义的 lang_var语言定义

***5.10.2数据分页
通常在数据查询后都会对数据集进行分页操作,ThinkPHP也提供了分页类来对数据分页提供支持。
分页类位于扩展类库下面,需要先导入才能使用(关于如何导入扩展类库,请参考扩展指南部分内容),下面是数据分页的两种示例。
第一种分页方法是利用Page类和limit方法:
$User = M(“User”); // 实例化User对象
import(“ORG.Util.Page”); // 导入分页类
$count      = $User->where(“status=1″)->count(); // 查询满足要求的总记录数
$Page       = new Page($count,25); // 实例化分页类传入总记录数和每页显示的记录数
$show       = $Page->show(); // 分页显示输出
// 进行分页数据查询注意limit方法的参数要使用Page类的属性
$list = $User->where(‘status=1′)->order(‘create_time’)->limit($Page->firstRow.’,’.$Page->listRows)->select();
$this->assign(‘list’,$list); // 赋值数据集
 $this->assign(‘page’,$show); // 赋值分页输出
$this->display(); // 输出模板
 
另外一种方式是分页类和page方法的实现
$User = M(“User”); // 实例化User对象
// 进行分页数据查询注意page方法的参数的前面部分是当前的页数使用 $_GET[p]获取
$list = $User->where(‘status=1′)->order(‘create_time’)->page($_GET[‘p’].’,25′)->select();
$this->assign(‘list’,$list); // 赋值数据集
import(“ORG.Util.Page”); // 导入分页类
$count      = $User->where(“status=1″)->count(); // 查询满足要求的总记录数
$Page       = new Page($count,25); // 实例化分页类传入总记录数和每页显示的记录数
$show       = $Page->show(); // 分页显示输出
$this->assign(‘page’,$show); // 赋值分页输出
$this->display(); // 输出模板
 
带入查询条件
如果是POST方式查询,如何确保分页之后能够保持原先的查询条件呢,我们可以给分页类传入参数,方法是给分页类的parameter属性赋值:
import(“ORG.Util.Page”); // 导入分页类
$mapcount      = $User->where($map)->count(); // 查询满足要求的总记录数
$Page       = new Page($count,25); // 实例化分页类传入总记录数和每页显示的记录数
//分页跳转的时候保证查询条件
 foreach($map as $key=>$val) {
$Page->parameter   .=   “$key=”.urlencode($val).”&”;
}
$show       = $Page->show(); // 分页显示输出
 

分页样式定制
默认的分页输出效果是
 
我们可以对输出的分页样式进行定制,分页类Page提供了一个setConfig方法来修改默认的一些设置。例如:
$page->setConfig(‘header’, ‘个会员’);
setConfig方法支持的属性包括:
header:头部描述信息,默认值 “条记录”
prev:上一页描述信息,默认值是“上一页”
next:下一页描述信息,默认值是“下一页”
first:第一页描述信息,默认值是“第一页”
last:最后一页描述信息,默认值是“最后一页”
theme:分页主题描述信息,包括了上面所有元素的组合 ,设置该属性可以改变分页的各个单元的显示位置,默认值是
“%totalRow% %header% %nowPage%/%totalPage%页 %upPage% %downPage% %first%  %prePage%  %linkPage%  %nextPage% %end%”
通过setConfig设置以上属性可以完美的定制出你的分页显示风格。

***5.10.3文件上传
上传类使用ORG类库包中的Net.UpdateFile类,最新版本的上传类包含的功能如下(有些功能需要结合ThinkPHP系统其他类库):
 基本上传功能
 支持批量上传
 支持生成图片缩略图
 自定义参数上传
 上传检测(包括大小、后缀和类型)
 支持覆盖方式上传
 支持上传类型、附件大小、上传路径定义
 支持哈希或者日期子目录保存上传文件
 上传图片的安全性检测
 支持上传文件命名规则
 支持对上传文件的Hash验证
在ThinkPHP中使用上传功能无需进行特别处理。例如,下面是一个带有附件上传的表单提交:
<form METHOD=POST action=”__URL__/upload” enctype=”multipart/form-data” >
<input type=”text” NAME=”name”  >
<input type=”text” NAME=”email”  >
<input type=”file”  name=”photo” >
<input type=”submit” value=”保 存” >
</form>
注意表单的Form标签中一定要添加 enctype=”multipart/form-data”文件才能上传。因为表单提交到当前模块的upload操作方法,所以我们在模块类里面添加下面的upload方法即可:
Public function upload(){
import(“ORG.Net.UploadFile”);
$upload = new UploadFile(); // 实例化上传类
$upload->maxSize  =3145728 ; // 设置附件上传大小
$upload->allowExts  = array(‘jpg’, ‘gif’, ‘png’, ‘jpeg’); // 设置附件上传类型
$upload->savePath =  ‘./Public/Uploads/'; // 设置附件上传目录
if(!$upload->upload()) { // 上传错误提示错误信息
$this->error($upload->getErrorMsg());
}else{ // 上传成功获取上传文件信息
$info =  $upload->getUploadFileInfo();
 }
// 保存表单数据包括附件数据
$User = M(“User”); // 实例化User对象
$User->create(); // 创建数据对象
$User->photo = $info[0][“savename”]; // 保存上传的照片根据需要自行组装
$User->add(); // 写入用户数据到数据库
$this->success(“数据保存成功!”);
}
首先是实例化上传类
import(“ORG.Net.UploadFile”);
$upload = new UploadFile(); // 实例化上传类
实例化上传类之后,就可以设置一些上传的属性(参数),支持的属性有:
maxSize: 文件上传的最大文件大小(以字节为单位)默认为-1不限大小
savePath:文件保存路径,如果留空会取UPLOAD_PATH常量定义的路径
saveRule:上传文件的保存规则,必须是一个无需任何参数的函数名,例如可以是 time、 uniqid com_create_guid等,但必须能保证生成的文件名是唯一的,默认是uniqid
hashType:上传文件的哈希验证方法,默认是md5_file
autoCheck:是否自动检测附件,默认为自动检测
uploadReplace:存在同名文件是否是覆盖
allowExts:允许上传的文件后缀(留空为不限制),使用数组设置,默认为空数组
allowTypes:允许上传的文件类型(留空为不限制),使用数组设置,默认为空数组
thumb:是否需要对图片文件进行缩略图处理,默认为false
thumbMaxWidth:缩略图的最大宽度,多个使用逗号分隔
thumbMaxHeight:缩略图的最大高度,多个使用逗号分隔
thumbPrefix:缩略图的文件前缀,默认为thumb_
thumbSuffix:缩略图的文件后缀,默认为空
thumbPath:缩略图的保存路径,留空的话取文件上传目录本身
thumbFile:指定缩略图的文件名
thumbRemoveOrigin:生成缩略图后是否删除原图
autoSub:是否使用子目录保存上传文件
subType:子目录创建方式,默认为hash,可以设置为hash或者date
dateFormat:子目录方式为date的时候指定日期格式
hashLevel:子目录保存的层次,默认为一层
以上属性都可以直接设置,例如:
$upload->thumb = true
$upload->thumbMaxWidth = “50,200”
$upload->thumbMaxHeight = “50,200”
其中生成缩略图功能需要Image类的支持。
设置好上传的参数后,就可以调用UploadFile类的upload方法进行附件上传,如果失败,返回false,并且用getErrorMsg方法获取错误提示信息;如果上传成功,可以通过调用getUploadFileInfo方法获取成功上传的附件信息列表。因此getUploadFileInfo方法的返回值是一个数组,其中的每个元素就是上传的附件信息。每个附件信息又是一个记录了下面信息的数组,包括:
key:附件上传的表单名称
savepath:上传文件的保存路径
name:上传文件的原始名称
savename:上传文件的保存名称
size:上传文件的大小
type:上传文件的MIME类型
extension:上传文件的后缀类型
hash:上传文件的哈希验证字符串
文件上传成功后,就可以通过这些附件信息来进行其他的数据存取操作,例如保存到当前数据表或者单独的附件数据表都可以。
如果需要使用多个文件上传,只需要修改表单,把
<input type=”file”  name=”photo” >
改为
<input type=”file”  name=”photo1″ >
<input type=”file”  name=”photo2″ >
<input type=”file”  name=”photo3″ >
或者
<input type=”file”  name=”photo[]” >
<input type=”file”  name=”photo[]” >
<input type=”file”  name=”photo[]” >
两种方式的多附件上传系统的文件上传类都可以自动识别。

***5.10.4验证码
要使用验证码,需要导入扩展类库中的ORG.Util.Image类库和ORG.Util.String类库。我们通过在在模块类中增加一个verify方法来用于显示验证码:
Public function verify(){
import(“ORG.Util.Image”);
Image::buildImageVerify();
}
Image类的buildImageVerify方法用于生成验证码,该方法有以下参数可选:
buildImageVerify($length,$mode,$type,$width,$height,$verifyName)
length:验证码的长度,默认为4位数
mode:验证字符串的类型,默认为数字,其他支持类型有0字母 1数字 2大写字母 3小写字母 4中文 5混合(去掉了容易混淆的字符oOLl和数字01)
type:验证码的图片类型,默认为png
width:验证码的宽度,默认会自动根据验证码长度自动计算
height:验证码的高度,默认为22
verifyName:验证码的SESSION记录名称,默认为verify
定义完成后,验证码的显示只需要在模板文件中添加:
<img src=”__APP__/Public/verify/” />
运行后可以看到类似下面的验证码显示:
 
每次生成验证码的时候,就会通过SESSION记录本次的验证码的md5后的字符串信息,所以,要检查验证码是否正确,我们只需要在Action中使用下面的代码就行了:
       if($_SESSION[‘verify’] != md5($_POST[‘verify’])) {
      $this->error(‘验证码错误!’);
 }
注意,这里的verify名称取决于你的验证码的verifyName参数的值。
 
buildImageVerify方法不支持中文验证码的显示,如果需要显示中文验证码,请使用
GBVerify方法,参数如下:
GBVerify ($length,$type,$width,$height,$fontface,$verifyName)
length:验证码的长度,默认为4位数
type:验证码的图片类型,默认为png
width:验证码的宽度,默认会自动根据验证码长度自动计算
height:验证码的高度,默认为50
fontface:使用的字体文件,使用完整文件名或者放到图像类所在的目录下面,默认使用的字体文件是simhei.ttf(该文件可以从window的Fonts目录下面找到)
verifyName:验证码的SESSION记录名称,默认为verify
例如
Public function verify(){
import(“ORG.Util.Image”);
Image::GBVerify();
}
显示效果如下:
 
如果无法显示验证码,请检查:
 PHP是否已经安装GD库支持;
 输出之前是否有任何的输出(尤其是UTF8的BOM头信息输出);
 Image类库是否正确导入;
如果是中文验证码检查是否有拷贝字体文件到类库所在目录;

***6扩展指南
***6.1类库扩展
***6.1.1基类库扩展
ThinkPHP的基类库目录位于ThinkPHP\Lib,默认的基类库只包含Think类库包。系统基类库可以很方便的进行扩展,目前支持的类库包包括ORG(第三方公共类库包)和Com(企业类库包)。你可以在ORG类库目录下面添加自己需要的类库(ThinkPHP基类库的所有类库文件统一使用class.php作为后缀,并且文件名和类名相同),你甚至还可以创建属于自己企业的类库,只需要在ThinkPHP\Lib\目录下面创建Com目录,然后在里面增加相应的类库就可以方便的使用import方法导入了。
例如,我们在ThinkPHP\Lib\Com\下面创建了Sina目录,并且放了Util\UnitTest.class.php类库文件,可以使用下面的方式导入
import(‘Com.Sina.Util.UnitTest’);

***6.1.2应用类库扩展
项目类库的扩展,和基类库的扩展一样,我们可以在项目类库目录增加你想要的子目录,例如,我们在MyApp的项目目录下面增加Common和Util目录,就可以这样加载这些目录下面的类库文件了:
import(‘MyApp.Util.UnitTest’);
import(‘@.Common.CommonUtil’);

***6.1.3第三方类库扩展
如果你直接使用的是第三方的类库包,或者是类名和后缀和ThinkPHP的默认规则不符合的,我们建议你放到ThinkPHP\Vendor目录下面,并使用vendor方法来导入。
例如,我们把Zend的Filter\Dir.php放到Vendor目录下面,这个时候Dir文件的路径就是
Vendor\Zend\Filter\Dir.php,我们使用vendor方法导入就是:
Vendor(‘Zend.Filter.Dir’);

***6.2应用扩展
应用扩展是指不改变现有底层框架的基础上,对App类进行额外的功能扩展,系统使用了标签扩展的方式。要启用应用扩展支持,必须在项目配置文件里面开启APP_PLUGIN_ON配置参数。
‘APP_PLUGIN_ON’=>true,
一旦开启后,系统就会检查下面的标签:
 app_begin:应用开始标签
 app_init:应用初始化标签
 app_run:应用执行标签
 app_end:应用结束标签
以上是系统的App应用类内置的一些标签位置,在每个定义的标签位置,都会执行一个tag方法来调用该标签位置需要执行的方法,例如:
我们可以看到,标签的执行只是一个很简单的代码,例如:
//执行应用初始化标签
tag(‘app_init’);
系统执行到这里的时候,会自动检查标签所对应要执行的方法,并且依次执行。
标签对应的执行方法是通过标签配置定义文件,在项目的配置目录下面增加tags.php文件,写入:
return array(
//定义项目初始化标签要执行的方法
‘app_init’=>array(
‘function1′,’function2′,array(‘class1′,’method1′)…
),
…//其他的标签
);
如果某个标签位置需要传入额外的参数,可以使用 tag(‘app_init’,$data);
会自动传入要执行的方法,注意参数必须一致才能准确调用。
应用标签扩展的方式,其实可以延伸到项目中,我们可以在项目的某些位置手动插入标签位,然后定义外部的标签扩展来执行。根据这样的一个原理,标签扩展可以随意定制。你需要做的仅仅是在需要执行的位置 加上 tag(‘标签名称'[,’可选参数’…])即可,然后在tags.php文件里面定义好各个标签要执行的方法,其他的事情系统会自动处理。

***6.3控制器扩展
***6.3.1模块扩展
模块扩展可以使得项目方便的动态挂载模块,动态模块只需要在项目配置目录下面定义模块定义文件modules.php,定义格式为:
return array(
‘moduleName’ => array(‘导入路径'[, ‘类名’]),
);
例如,我们定于了一个名称为Extend的扩展模块,其模块类的文件路径位于项目的Lib\Modules\ExtendAction.class.php,那么定义如下:
return array(
‘Extend’ => array(‘@.Modules.Extend’),
);
一般情况下,类名无需指定,会按照默认的规则去找,如果你的类名和系统规则不一致,就需要指定类名,假如模块文件名是Extend.class.php:
return array(
‘Extend’ => array(‘@.Modules.Extend’, ‘Extend’]),
);
 
  注意事项:
扩展的模块一定是现有项目里面没有的,否则无效;
更改扩展模块定义后,需要删除项目编译缓存文件;
动态模块的规则比空模块的规则要优先。

***6.3.2操作扩展
操作扩展可以使得项目方便的动态挂载某些操作,而这些操作可以是针对个别模块的,也可以是针对全局的。操作扩展只需要在项目配置目录下面定义操作定义文件actions.php,定义格式为:
return array(
//全局操作
‘actionName’ => ‘调用方法’,
//局部操作
‘moduleName:actionName’ => ‘调用方法’,
);
其中调用方法可以是某个自定义的函数,或者是某个类的方法,类似于PHP的callback定义。
  注意事项:
扩展的操作一定是现有项目里面没有的,否则无效;
更改操作扩展定义后,需要删除项目编译缓存文件;
动态操作的规则比空操作的规则要优先。

***6.4模型扩展
ThinkPHP的新版模型具有很好的扩展性,对模型的CURD方法都提供了扩展接口。
模型类提供了多个回调接口,主要扩展接口如下:
全局接口:
初始化接口_initialize()
表达式过滤接口 _options_filter(&$options)
add方法接口:
写入前置接口 _before_insert(&$data,$options)
写入后置接口 _after_insert($data,$options)
save方法接口:
更新前置接口 _before_update(&$data,$options)
更新后置接口 _after_update($data,$options)
同时对add和save方法的接口:
数据写入(包括写入和更新)数据库之前的处理接口 _facade($data)
delete方法接口:
删除后置接口_after_delete($data,$options)
select(findall)方法接口:
查询后置接口(数据集) _after_select(&$result,$options)
find方法接口:
查询后置接口(数据)_after_find(&$result,$options)
 
系统内置的高级模型AdvModel、视图模型ViewModel和关联模型RelationModel本身就是一个模型扩展的很好的例子,本身都是继承Model类并且都通过了扩展实现很多的功能。

***6.5驱动扩展
数据库抽象层的设计是由抽象数据库操作类和数据库驱动类组成的,内置的数据库驱动是MySQL和MySQLi驱动类,官方的扩展还提供了MsSQL、PgSQL、Sqlite、Oracle、Ibase以及PDO驱动类,可以满足常用的数据库操作的需要。
要扩展其他的数据库驱动类,只需要继承Db类,驱动类的命名规范是:
Db+驱动类名称(首字母大写)
例如,假如你需要扩展一个ODBC的数据库驱动,应该命名为:DbOdbc.class.php,并放到系统的Lib\Think\Db\Driver目录下面。
Class DbOdbc  extends Db{
}
然后,需要使用的时候,设置相应的数据库类型即可:
‘DB_TYPE’=>’Odbc’, //数据库类型配置不区分大小写
每个数据库驱动需要实现的方法包括(具体参数可以参考现有的数据库驱动类库):
 架构和析构方法
 Connect连接数据库方法
 Free释放查询方法
 Query查询操作方法
 Execue执行操作方法
 startTrans开启事务方法
 commit事务提交方法
 rollback事务回滚方法
 getAll获取查询数据方法
 getFields取得数据表的字段信息
 getTables取得数据库的表信息
 close关闭数据库连接方法
 error获取数据库错误信息
escape_string SQL安全过滤方法

***6.6Widget扩展
Widget扩展用于在页面根据需要输出不同的内容,Widget扩展的定义是在项目的Lib\Widget目录下面定义Widget类库,例如下面定义了一个用于显示最近的评论的Widget:
位于Lib\Widget\ShowCommentWidget.class.php
Widget类库需要继承Widget类,并且必须定义render方法实现,例如:
class ShowCommentWidget extends Widget{
    public function render($data){
        return’这是最新的评论信息';
    }
}
render方法必须使用return返回要输出的字符串信息,而不是直接输出。
Widget也可以调用Widget类的renderFile方法,渲染模板后进行输出,。
class ShowCommentWidget extends Widget{
    public function render($data){
$content    =  $this->renderFile(‘Article:comment’,$data);
  return$content;
    }
}
定义好Widget类库后,只需要做的是在模板文件里面使用W方法调用Widget,例如
{:W(‘ShowComment’)}
通常Widget都有自己的调用参数来决定不同的输出内容
{:W(‘ShowComment’,array(‘count’=>5))}
参数必须使用索引数组传入。
在控制器里面也可以调用Widget类进行输出,在Action里面获取动态的Widget内容,可以使用下面的方式:
$content    =  W(‘ShowComment’, array(‘count’=>5),true);
第三个参数表示是否返回字符串,如果是false就表示直接输出。返回值可以用于其他用途。

***6.7行为扩展
行为扩展和Widget扩展的区别其实就是Widget是用于输出的,而行为通常是执行某个方法,但通常都不需要输出,即使输出的话也许是错误提示信息之类的。
行为是可以和应用扩展配合的,因为应用扩展是很随意的,但是行为却是可以规范的。定义好的行为扩展,可以被任何应用扩展中的标签单独调用。
行为类的定义也很简单,例如下面是一个代理检测访问行为的扩展:
class AgentCheckBehavior extends Behavior {
    public function run() {
        //代理访问检测
        if(C(‘LIMIT_PROXY_VISIT’) && ($_SERVER[‘HTTP_X_FORWARDED_FOR’] || $_SERVER[‘HTTP_VIA’] || $_SERVER[‘HTTP_PROXY_CONNECTION’] || $_SERVER[‘HTTP_USER_AGENT_VIA’])) {
            //禁止代理访问
            exit(‘Access Denied’);
        }
    }
}
行为类必须定义一个run接口方法,否则无法正确调用。
命名为AgentCheckBehavior.class.php后 放入项目的Lib\Behavior目录下面。
 
接下来就是调用这个行为,在调用的地方只需要使用:
B(‘AgentCheck’);
配合应用扩展机制的话,例如我们在项目初始化标签的执行方法里面使用了上面的代码,就会在项目初始化的时候自动调用该行为了。
***6.8标签库扩展
***6.8.1标签库原理
任何一个模板引擎的功能都不可能是为你量身定制的,具有一个良好的可扩展机制也是模板引擎的另外一个考量,Smarty采用的是插件方法来实现扩展,ThinkTemplate由于采用了标签库技术,比Smarty提供了更为强大的定制功能,和Java的TagLibs一样可以支持自定义标签库和标签,每个XML标签都有独立的解析方法,所以可以根据标签库的定义规则来增加和修改标签解析规则。在ThinkTemplate中标签库的体现是采用XML命名空间的方式。
标签库由定义文件和解析类构成。每个标签库存在一个XML定义文件,用来定义标签库中的标签和属性。并且一个标签库文件对应一个标签库解析类,每个标签就是解析类中的一个方法。例如,CX标签库的定义文件是cx.xml位于ThinkTemplate/Template/Tags/目录下面,而cx标签库解析类文件是位于ThinkTemplate/Template/TagLib/目录下面的TagLibCx.class.php文件,每个标签的解析方法就是TagLibCx类的一个方法,为了不和系统的关键字冲突,所以在方法名前加上了“_”前缀,因此,假如要定义Cx:Var的标签解析,就需要定义一个 _var方法。
标签库解析类的作用其实就是把某个标签定义解析成为有效的模版文件(可以包括PHP语句或者HTML标签)。扩展标签库需要添加标签库定义XML文件和标签库解析类。
标签库定义XML文件的格式为:
<?xml version=”1.0″ encoding=”UTF-8″?>
<taglib>
<tag>
<name>标签名称</name>
<nested>是否允许嵌套</nested>
<alias>标签别名</alias>
<bodycontent>是否属于闭合标签</bodycontent>
<attribute>
<name>属性名称</name>
<required>是否必须</required>
</attribute>
</tag>
</taglib>
标签库的名称和文件名一致,每个tag标签对定义了标签库中的一个标签,每个tag节点的属性定义规范如下:
 name:标签名称
 nested:是否允许标签嵌套(true或false)
 alias:标签别名(多个逗号分隔)
 bodycontent:是否为闭合标签(true或empty)
 attribute:标签允许的属性
每个标签节点可以包含多个属性,也就是tag节点可以定义多个attribute节点,每个attribute属性支持两个属性:name和required,required(true或false)表示该属性是否为必须。
然后,我们看解析类的定义,每个标签的解析方法在定义的时候需要添加“_”前缀,可以传入两个参数,属性字符串和内容字符串(对于非闭合标签)。必须通过return返回标签的字符串解析输出,在标签解析类中可以调用模板类的实例。下面是一个include解析方法的定义:
    public function _include($attr,$content)
    {
        $tag    = $this->parseXmlAttr($attr,’include’);
        $file   =   $tag[‘file’];
        return $this->tpl->parseInclude($file);
    }
在每个标签的解析方法中,首先需要调用
 $this->parseXmlAttr($attr,’include’);
表示分析某个标签的XML定义,返回include的所有标签属性。接下来就是根据具体的属性值来返回实际的解析内容了。

******6.8.2普通标签扩展
普通标签的扩展可以通过配置TAG_EXTEND_PARSE参数来进行,例如,我要扩展一个下面的js标签实现:
{js:/Public/Js/Think.js}
希望能够实现加载/Public/Js/Think.js的结果。
我们可以在项目配置文件中定义:
TAG_EXTEND_PARSE=>array(
       “js”=>”parse_js_load”,   //定义js标签的解析方法 js必须使用小写定义
)
然后我们在项目的函数库里面添加parse_js_load函数,如下:
function parse_js_load($str){
return  ‘<script type=”text/javascript” src=”‘.$str.'”></script>';
}
在删除项目的编译缓存文件~app.php后,就可以使用hello普通标签了。虽然这个例子非常简单,但是只要理解以后就可以扩展出功能强大的普通标签了。

***6.8.3扩展标签库
要扩展标签库,有两种方式:
第一种,直接把标签库放入系统的标签库目录。
首先,把标签库的定义文件放入系统的标签库定义目录Lib/Think/Template/Tags/。把标签库的解析类库放入Lib/Think/Template/TagLib/目录。
然后在模板页面添加:
<taglib name=’标签库名称’ />
这样就可以直接使用扩展的标签库了。
 
第二种,通过配置的方式加载标签库。
这种方式需要在项目配置文件里面定义taglibs.php文件,格式如下:
return array(
‘标签库1’=>’标签库1解析类库路径’, // 使用import方法支持的路径格式
‘标签库2’=>’标签库2解析类库路径’, 

);
例如:
return array(
‘mytag’=>’@.TagLib.TagLibMytag’,
);
然后在项目的Lib\TagLib\目录下面,增加一个 TagLibMytag.class.php标签库解析文件,
标签库解析类的命名是:TagLib+标签库名称(首字母大写)
标签库定义文件可以放在Lib\TagLib\Tags\下面或者自己定义(参考下面的初始化方法),名称通常是标签库的名称。
class TagLibMytag.class.php extends TagLib{
    //初始化标签库的定义文件
    public function _initialize() {
        $this->xml = dirname(__FILE__).’/Tags/mytag.xml';
    }
}
定义_initialize方法的目的是定位标签库的定义XML文件,这样标签库就可以完全独立系统在项目中存在了。

***6.9模板引擎扩展
除了使用内置的模板引擎外,系统还支持模板引擎扩展。并且官方已经提供了包括Smarty、EaseTemplate、TemplateLite和Smart在内的第三方模板引擎扩展。
模板引擎扩展类库的命名为:Template+模板引擎名称(首字母大写)
模板引擎扩展类只需要实现一个fetch接口方法,参数为:
function fetch(模板文件,模板变量,模板编码)
例如:
    public function fetch($templateFile,$var,$charset) {
        $templateFile=substr($templateFile,strlen(TMPL_PATH));
        vendor(‘Smarty.Smarty#class’);
        $tpl = new Smarty();
        if(C(‘TMPL_ENGINE_CONFIG’)) {
            $config  =  C(‘TMPL_ENGINE_CONFIG’);
            foreach ($config as $key=>$val){
                $tpl->{$key}   =  $val;
            }
        }else{
            $tpl->caching = C(‘TMPL_CACHE_ON’);
            $tpl->template_dir = TMPL_PATH;
            $tpl->compile_dir = CACHE_PATH ;
            $tpl->cache_dir = TEMP_PATH ;
        }
        $tpl->assign($var);
        $tpl->display($templateFile);
    }
如果扩展类库中需要涉及到第三方类库,可以放到Vendor目录下面,以供调用。
要使用扩展模板引擎的话,只需要在项目配置文件中添加:
‘TMPL_ENGINE_TYPE’ => ‘模板引擎名称’
就可以使用对应的模板引擎来定义模板文件了。影响是只是模板文件的定义,视图操作方法保持原来的不变。例如在Action中用assign赋值模板变量、display和fetch方法的使用、模板文件的定位规则、模板替换功能仍然一致。

***6.10模式扩展
***6.10.1使用内置的模式
我们前面所涉及的所有用法都是基于框架的标准模式的,除了标准模式之外,官方的发布版本还内置了几种常用的模式扩展,包括:Cli(命令模式)、Lite(精简模式)、Thin(简洁模式),他们为不同的需求提供了不同的底层框架解决方案。通常来说不同的模式之间是无法进行切换,下面阐述下这几种模式和标准模式的区别:
Thin模式:简洁模式
主要区别在于:
 默认不使用任何模板引擎(可以自己在操作方法里面调用);
 模型仅支持原生SQL操作和事务;
 支持多数据库切换和连接;
 默认仅支持MySQL数据库;
 不支持语言包、模块分组、模板主题和Dispatch功能;
 去除了大部分扩展机制;
如果你的应用选择了Mysql数据库,并且完全使用原生SQL操作,并希望有一个轻巧的核心,那么简洁模式是一个很好的选择。
要使用简洁模式,需要在项目的入口文件中添加模式定义:
 define(‘THINK_MODE’,’Thin’);  // 采用简洁模式运行
 
Lite模式:精简模式
在简洁模式的基础上,增加了:
 默认使用PHP模板;
 支持不带路由的Dispatch;
 支持不带回调接口的CURD操作;
 支持连贯操作、统计查询;
精简模式比简洁模式在模型方面多了CURD和连贯操作,如果你习惯于使用PHP作为模板,并且还是喜欢使用模型的CURD功能,但又不希望核心那么庞大,那么精简模式是一个不错的选择。
要使用精简模式,需要在项目的入口文件中添加模式定义:
define(‘THINK_MODE’,’Lite’);  // 采用精简模式运行
 
Cli模式:命令行模式
和简洁模式基本类似,只是支持命令行下面的参数解析。
要使用命令行模式,需要在项目的入口文件中添加模式定义:
define(‘THINK_MODE’,’Cli’);  // 采用命令模式运行

***6.10.1定制模式扩展
模式扩展的本质其实就是组装自己的新的基于ThinkPHP的框架核心。这是新版框架底层可组装可定制的重要思想。基于ThinkPHP的意思是能够使用ThinkPHP的内置函数方法、配置方式、独特的编译缓存机制,以及一些常量定义。事实上模式扩展可以让你完全抛开系统内置的MVC方式和核心类库~这也许是新版的魅力所在。
而作为新版的框架核心可组装的概念来说,主要体现在如下几个方面:
1、框架的目录结构和路径可定义;
2、框架的核心编译文件列表可定义;
3、框架的MVC可定义(也就是模式扩展了~)
模式扩展完全可以通过在项目里面定义自己的核心编译文件列表的方式来取代,因为其效果是等效的。只不过一个是可以通过设置模式来运行,而另外一个则是读取项目自身的定义,而且项目自身定义的优先级大于模式设置。
模式扩展主要是定义一个模式加载文件,例如简洁模式的thin.php文件定义方式如下:
return array(
    THINK_PATH.’/Lib/Think/Exception/ThinkException.class.php’,//异常处理
    THINK_PATH.’/Lib/Think/Core/Log.class.php’,//日志处理
    THINK_PATH.’/Mode/Thin/App.class.php’, //应用程序类
    THINK_PATH.’/Mode/Thin/Action.class.php’,//控制器类
    THINK_PATH.’/Mode/Thin/alias.php’, //加载别名
);
通过这样的定义,简洁模式加载了自己定义的App、Action和Model类库(在alias中定义了Model的别名加载),并且沿用了核心自带的异常和日志处理类库。最后一个别名定义文件是可选的~用于快速加载类库文件,这也是新版的一个功能改进。
仔细看了简洁模式的App类定义和Model定义后,就大概明白了简洁模式的工作原理了,作了很多功能上的简化。
项目核心列表文件位于项目的配置目录(默认是Conf),名称为core.php。其定义方式和模式扩展文件类似,区别在于核心文件的位置不同。
如果我们需要为自己的项目量身定制一个底层框架核心,又不想改变项目的入口文件定义,那么还可以根据自己的要求来进行项目核心扩展。
例如,我们在项目的配置目录下面增加一个core.php文件,定义如下:
return array(
    LIB_PATH.’/Mode/App.class.php’, //应用程序类
    LIB_PATH.’/Mode/Action.class.php’,//控制器类
    LIB_PATH.’/Mode/alias.php’, //加载别名
);
然后就是根据自己的实际需要,在项目的应用类库目录下面创建Mode目录,放入相应的定制后的App、Action和Model类,包括异常处理和日志类都是可以替换的。
定义完成后,需要清空核心编译缓存和项目编译缓存才能生效。

***7模型指南
***7.1变量输出
我们已经知道了在Action中使用assign方法可以给模板变量赋值,赋值后怎么在模板文件中输出变量的值呢?
如果我们在Action中赋值了一个name模板变量:
$name = ‘ThinkPHP';
$this->assign(‘name’,$name);
使用内置的模板引擎输出变量,只需要在模版文件使用:
{$name}
模板编译后的结果就是
<?php echo($name);?>
最后运行的时候就可以在标签位置显示ThinkPHP的输出结果。
注意模板标签的{和$之间不能有任何的空格,否则标签无效。
普通标签默认开始标记是 {,结束标记是 }。也可以通过设置TMPL_L_DELIM和TMPL_R_DELIM进行更改。例如,我们在项目配置文件中定义:
‘TMPL_L_DELIM’=>'<{‘,
‘TMPL_R_DELIM’=>’}>’,
那么,上面的变量输出标签就应该改成:
<{$name}>
后面的内容我们都以默认的标签定义来说明。
assign方法里面的第一个参数才是模板文件中使用的变量名称。如果改成下面的代码:
$name = ‘ThinkPHP';
$this->assign(‘name2′,$name);
再使用{$name}输出就无效了,必须使用 {$name2}才能输出模板变量的值了。
 
如果我们需要把一个用户数据对象赋值给模板变量:
$User = M(‘name’);
$user = $User->find(1);
$this->assign(‘user’,$user);
也就是说$user其实是一个数组变量,我们可以使用下面的方式来输出相关的值:
{$user[‘name’]} // 输出用户的名称
{$user[’email’]} // 输出用户的email地址
 
如果$user是一个对象而不是数组的话,
$User = M(‘name’);
$User->find(1);
$this->assign(‘user’,$User);
可以使用下面的方式输出相关的属性值:
{$user:name}// 输出用户的名称
{$user:email}  // 输出用户的email地址
 
为了方便模板定义,还可以支持点语法,例如,上面的
{$user[‘name’]} // 输出用户的名称
{$user[’email’]} // 输出用户的email地址
可以改成
{$user.name}
{$user.email}
因为点语法默认的输出是数组方式,所以上面两种方式是在没有配置的情况下是等效的。我们可以通过配置TMPL_VAR_IDENTIFY参数来决定点语法的输出效果,以下面的输出为例:
{$user.name}
如果TMPL_VAR_IDENTIFY设置为array,那么
{$user.name}和{$user[‘name’]}等效,也就是输出数组变量。
如果TMPL_VAR_IDENTIFY设置为obj,那么
{$user.name}和{$user:name}等效,也就是输出对象的属性。
如果TMPL_VAR_IDENTIFY留空的话,系统会自动判断要输出的变量是数组还是对象,这种方式会一定程度上影响效率,而且只支持二维数组和两级对象属性。
如果是多维数组或者多层对象属性的输出,可以使用下面的定义方式:
{$user.sub.name}  // 使用点语法输出
或者使用
{$user[‘sub’][‘name’]} // 输出三维数组的值
{$user:sub:name}// 输出对象的多级属性

***7.2使用函数
仅仅是输出变量并不能满足模板输出的需要,内置模板引擎支持对模板变量使用调节器和格式化功能,其实也就是提供函数支持,并支持多个函数同时使用。用于模板标签的函数可以是PHP内置函数或者是用户自定义函数,和smarty不同,用于模板的函数不需要特别的定义。
模板变量的函数调用格式为:
{$varname|function1|function2=arg1,arg2,### }
说明:
{和 $符号之间不能有空格 ,后面参数的空格就没有问题
###表示模板变量本身的参数位置
支持多个函数,函数之间支持空格
支持函数屏蔽功能,在配置文件中可以配置禁止使用的函数列表
支持变量缓存功能,重复变量字串不多次解析
使用例子:
{$webTitle|md5|strtoupper|substr=0,3}
编译后的PHP代码就是:
<?php echo (substr(strtoupper(md5($webTitle)),0,3)); ?>
注意函数的定义和使用顺序的对应关系,通常来说函数的第一个参数就是前面的变量或者前一个函数使用的结果,如果你的变量并不是函数的第一个参数,需要使用定位符号,例如:
{$create_time|date=”y-m-d”,###}
编译后的PHP是:
<?php echo (date(“y-m-d”,$create_time)); ?>
函数的使用没有个数限制,但是可以允许配置TMPL_DENY_FUNC_LIST定义禁用函数列表,系统默认禁用了exit和echo函数,以防止破坏模板输出,我们也可以增加额外的定义,例如:
TMPL_DENY_FUNC_LIST=>”echo,exit,halt”
多个函数之间使用半角逗号分隔即可。
 
并且还提供了在模板文件中直接调用函数的快捷方法,无需通过模板变量,包括两种方式:
1、执行方法并输出返回值:
格式:{:function(…)}
例如,输出U方法的返回值:
{:U(‘User/insert’)}
编译后的PHP代码是
<?php echo U(‘User/insert’);?>
 
2、执行方法但不输出:
格式:{~function(…)}
例如,调用say_hello函数:
{~say_hello(‘ThinkPHP’)}
编译后的PHP代码是:
<?php say_hello(‘ThinkPHP’);?>

***7.3系统变量
除了常规变量的输出外,模板引擎还支持系统变量和系统常量、以及系统特殊变量的输出。它们的输出不需要事先赋值给某个模板变量。系统变量的输出必须以$Think.打头,并且仍然可以支持使用函数。
1、系统变量:包括server、session、post、get、request、cookie
{$Think.server.script_name } //输出$_SERVER变量
{$Think.session.session_id|md5 } // 输出$_SESSION变量
{$Think.get.pageNumber } //输出$_GET变量
{$Think.cookie.name }  //输出$_COOKIE变量
支持输出$_SERVER、$_ENV、 $_POST、 $_GET、 $_REQUEST、$_SESSION和 $_COOKIE变量。后面的server、cookie、config不区分大小写,但是变量区分大小写。例如:
{$Think.server.script_name }和{$Think.SERVER.script_name }等效
SESSION、COOKIE还支持二维数组的输出,例如:
{$Think.CONFIG.user.user_name}
{$Think.session.user.user_name}
系统不支持三维以上的数组输出,请使用下面的方式输出。
 
以上方式还可以写成:
{$_SERVER.script_name } //输出$_SERVER变量
{$_SESSION.session_id|md5 } // 输出$_SESSION变量
{$_GET.pageNumber } //输出$_GET变量
{$_COOKIE.name }  //输出$_COOKIE变量
 
2、系统常量 :使用$Think.const输出
{$Think.const.__SELF__ }
{$Think.const.MODULE_NAME }
或者直接使用
{$Think.__SELF__ }
{$Think.MODULE_NAME }
 
3、特殊变量 :由ThinkPHP系统内部定义的常量
{$Think.version }  //版本
{$Think.now } //现在时间
{$Think.template|basename } //模板页面
{$Think.LDELIM } //模板标签起始符号
{$Think.RDELIM } //模板标签结束符号
 
4、配置参数 :输出项目的配置参数值
{$Think.config.db_charset}
输出的值和C(‘db_charset’)的返回结果是一样的。
也可以输出二维的配置参数,例如:
{$Think.config.user.user_name}
 
5、语言变量:输出项目的当前语言定义值
{$Think.lang.page_error}
输出的值和L(‘page_error’)的返回结果是一样的。

***7.4快捷输出
为了使得模板定义更加简洁,系统还支持一些常用的变量输出快捷标签,包括:
{@var} //输出Session变量 和 {$Think.session.var}等效
{#var} //输出Cookie变量 和 {$Think.cookie.var}等效
{&var} //输出配置参数 和 {$Think.config.var}等效
{%var} //输出语言变量 和 {$Think.lang.var}等效
{.var} //输出GET变量 和 {$Think.get.var}等效
{^var} //输出POST变量 和{$Think.post.var}等效
{*var} //输出常量和 {$Think.const.var}等效
如果需要输出二维数组,例如 要输出$_SESSION[‘var1’][‘var2’]的值 快捷输出可以使用:
{@var1.var2}的方式
同理
{#var1.var2}
可以输出 $_COOKIE[‘var1’][‘var2’]的值。
必须注意的是:快捷输出的变量不支持函数的使用。
所以,下面的用法是错误的:
{#var|strlen}

***7.5默认值输出
如果输出的模板变量没有值,但是我们需要在显示的时候赋予一个默认值的话,可以使用default语法,格式:
{$变量|default=”默认值”}
这里的default不是函数,而是系统的一个语法规则,例如:
{$user.nickname|default=”这家伙很懒,什么也没留下”}
对系统变量的输出也可以支持默认值,例如:
{$Think.post.name|default=”名称为空”}
因为快捷输出不支持使用函数,所以也不支持默认值,默认值支持Html语法。

***7.6包含文件
可以使用Include标签来包含外部的模板文件,使用方法如下:
1、 使用完整文件名包含
格式:<include file=”完整模板文件名” />
例如:
<include file=”./Tpl/default/Public/header.html” />
这种情况下,模板文件名必须包含后缀。使用完整文件名包含的时候,特别要注意文件包含指的是服务器端包含,而不是包含一个URL地址,也就是说file参数的写法是服务器端的路径,如果使用相对路径的话,是基于项目的入口文件位置。
 
2、包含当前模块的其他操作模板文件
格式:<include file=”操作名” />
例如 导入当前模块下面的read操作模版:
<include file=”read” />
操作模板无需带后缀。
 
3、 包含其他模块的操作模板
格式:<include file=”模块名:操作名” />
例如,包含Public模块的header操作模版:
<include file=”Public:header” />
 
4、包含其他模板主题的模块操作模板
格式:<include file=”主题名@模块名:操作名” />
例如,包含blue主题的User模块的read操作模版:
<include file=”blue@User:read” />
 
5、 用变量控制要导入的模版
格式:<include file=”$变量名” />
例如
<include file=”$tplName” />
给$tplName赋不同的值就可以包含不同的模板文件,变量的值的用法和上面的用法相同。
 
6、使用快捷方式包含文件
格式:{include:模板文件规则}
其中的模板文件规则可以使用上面提到的5种方式。
注意:由于模板解析的特点,从入口模板开始解析,如果外部模板有所更改,模板引擎并不会重新编译模板,除非缓存已经过期。如果修改了包含的外部模板文件后,需要把模块的缓存目录清空,否则无法生效。

***7.7导入文件
传统方式的导入外部JS和CSS文件的方法是直接在模板文件使用:
<script type=’text/javascript’ src=’/Public/Js/Util/Array.js’>
<link rel=”stylesheet” type=”text/css” href=”/App/Tpl/default/Public/css/style.css” />
 
系统提供了专门的标签来简化上面的导入:
第一个是import标签 ,导入方式采用类似ThinkPHP的import函数的命名空间方式,例如:
<import type=’js’ file=”Js.Util.Array” />
Type属性默认是js, 所以下面的效果是相同的:
<import file=”Js.Util.Array” />
还可以支持多个文件批量导入,例如:
<import file=”Js.Util.Array,Js.Util.Date” />
导入外部CSS文件必须指定type属性的值,例如:
<import type=’css’ file=”Css.common” />
上面的方式默认的import的起始路径是网站的Public目录,如果需要指定其他的目录,可以使用basepath属性,例如:
<import file=”Js.Util.Array”  basepath=”./Common” />
 
第二个是load标签,通过文件方式导入当前项目的公共JS或者CSS
例如:
<load href=”../Public/Js/Common.js” />
<load href=”../Public/Css/common.css” />
在href属性中可以使用特殊模板标签替换,例如:
<load href=”__PUBLIC__/Js/Common.js” />
Load标签可以无需指定type属性,系统会自动根据后缀自动判断。
系统还提供了两个标签别名js和css用法和load一致,例如:
<js href=”__PUBLIC__/Js/Common.js” />
<css href=”../Public/Css/common.css” />
 
另外,系统提供了普通标签的方式加载外部js和css文件。
{load: __PUBLIC__/Js/Common.js}
{load: ../Public/Css/common.css }

***7.8Volist标签
Volist标签主要用于在模板中循环输出数据集或者多维数组。
通常模型的select和findall方法返回的结果是一个二维数组,可以直接使用volist标签进行输出。
在Action中首先对模版赋值:
$User = M(‘User’);
$list = $User->select();
$this->assign(‘list’,$list);
 
在模版定义如下,循环输出用户的编号和姓名:
<volist name=”list” id=”vo”>
{$vo.id}
{$vo.name}
</volist>
Volist标签的name属性表示模板赋值的变量名称,因此不可随意在模板文件中改变。id表示当前的循环变量,可以随意指定,但确保不要和name属性冲突,例如:
<volist name=”list” id=”data”>
{$data.id}
{$data.name}
</volist>
支持输出部分数据,例如输出其中的第5~15条记录
<volist name=”list” id=”vo” offset=”5″ length=’10’>
{$vo.name}
</volist>
 
 输出偶数记录
<volist name=”list” id=”vo” mod=”2″ >
<eq name=”mod” value=”1″>{$vo.name}</eq>
</volist>
Mod属性还用于控制一定记录的换行,例如:
<volist name=”list” id=”vo” mod=”5″ >
{$vo.name}
<eq name=”mod” value=”4″><br/></eq>
</volist>
 
 输出循环变量
<volist name=”list” id=”vo” key=”k” >
{$k}.{$vo.name}
</volist>
如果没有指定key属性的话,默认使用循环变量i,例如:
<volist name=”list” id=”vo”  >
{$i}.{$vo.name}
</volist>
如果要输出数组的索引,可以直接使用key变量,和循环变量不同的是,这个key是由数据本身决定,而不是循环控制的,例如:
<volist name=”list” id=”vo”  >
{$key}.{$vo.name}
</volist>
volist还有一个别名iterate,用法和volist是一样。

***7.9Foreach标签
foreach标签也是用于循环输出
<foreach name=”list” item=”vo”>
{$vo.id}
{$vo.name}
</foreach>
Foreach标签相对比volist标签简洁,没有volist标签那么多的功能。优势是可以对对象进行遍历输出,而volist标签通常是用于输出数组。

***7.10Switch标签
模板引擎支持Switch标签,格式为:
<switch name=”变量” >
<case value=”值1″>输出内容1</case>
<case value=”值2″>输出内容2</case>
<default  />默认情况
</switch>
使用方法如下:
<switch name=”User.level”>
<case value=”1″>value1</case>
<case value=”2″>value2</case>
<default />default
</switch>
其中name属性可以使用函数以及系统变量,例如:
<switch name=”Think.get.userId|abs”>
<case value=”1″>admin</case>
<default />default
</switch>
对于case的value属性可以支持多个条件的判断,使用”|”进行分割,例如:
<switch name=”Think.get.type”>
<case value=”gif|png|jpg”>图像格式</case>
<default />其他格式
</switch>
表示如果$_GET[“type”]是gif、png或者jpg的话,就判断为图像格式。
也可以对case的value属性使用变量,例如:
<switch name=”User.userId”>
<case value=”$adminId”>admin</case>
<case value=”$memberId”>member</case>
<default />default
</switch>
使用变量方式的情况下,不再支持多个条件的同时判断。

***7.11比较标签
模板引擎提供了丰富的判断标签,比较标签的用法是:
<比较标签 name=”变量” value=”值”>内容</比较标签>
系统支持的比较标签以及所表示的含义分别是:
 eq或者 equal:等于
 neq或者notequal:不等于
 gt:大于
 egt:大于等于
 lt:小于
 elt:小于等于
 heq:恒等于
 nheq:不恒等于
他们的用法基本是一致的,区别在于判断的条件不同。
例如,要求name变量的值等于value就输出,可以使用:
<eq name=”name” value=”value”>value</eq>
或者
<equal name=”name” value=”value”>value</equal>
也可以支持和else标签混合使用:
<eq name=”name” value=”value”>相等<else/>不相等</eq>
 
当 name变量的值大于5就输出
<gt name=”name” value=”5″>value</gt>
当name变量的值不小于5就输出
<egt name=”name” value=”5″>value</egt>
比较标签中的变量可以支持对象的属性或者数组,甚至可以是系统变量:
举例说明:
 当vo对象的属性(或者数组,或者自动判断)等于5就输出
<eq name=”vo.name” value=”5″>{$vo.name}</eq>
当vo对象的属性等于5就输出
<eq name=”vo:name” value=”5″>{$vo.name}</eq>
当$vo[‘name’]等于5就输出
<eq name=”vo[‘name’]” value=”5″>{$vo.name}</eq>
而且还可以支持对变量使用函数
 当vo对象的属性值的字符串长度等于5就输出
<eq name=”vo:name|strlen” value=”5″>{$vo.name}</eq>
变量名可以支持系统变量的方式,例如:
<eq name=”Think.get.name” value=”value”>相等<else/>不相等</eq>
通常比较标签的值是一个字符串或者数字,如果需要使用变量,只需要在前面添加“$”标志:
当vo对象的属性等于$a就输出
<eq name=”vo:name” value=”$a”>{$vo.name}</eq>
所有的比较标签可以统一使用compare标签(其实所有的比较标签都是compare标签的别名),例如:
当name变量的值等于5就输出
 <compare name=”name” value=”5″ type=”eq”>value</compare>
等效于 <eq name=”name” value=”5″ >value</eq>
其中type属性的值就是上面列出的比较标签名称

***7.12Range标签
Range标签用于判断某个变量是否在某个范围之内,包括in、notin和range三个标签。
可以使用in标签来判断模板变量是否在某个范围内,例如:
<in name=”id”value=”1,2,3″>输出内容1</in>
如果判断不再某个范围内,可以使用:
<notin name=”id”value=”1,2,3″>输出内容2</notin>
可以把上面两个标签合并成为:
<in name=”id”value=”1,2,3″>输出内容1<else/>输出内容2</in>
Value属性的值可以使用变量,例如:
<in name=”id”value=”$var”>输出内容1</in>
变量的值可以是字符串或者数组,都可以完成范围判断。
也可以直接使用range标签,替换in和notin的用法:
<range name=”id”value=”1,2,3″type=”in”>输出内容1</range>
其中type属性的值可以用in或者notin。

***7.13Present标签
可以使用present标签来判断模板变量是否已经赋值,例如:
<present name=”name”>name已经赋值</present>
如果判断没有赋值,可以使用:
<notpresent name=”name”>name还没有赋值</notpresent>
可以把上面两个标签合并成为:
<present name=”name”>name已经赋值<else/> name还没有赋值</present>

***7.14Empty标签
可以使用empty标签判断模板变量是否为空,例如:
<empty name=”name”>name为空值</empty>
如果判断没有赋值,可以使用:
<notempty name=”name”>name不为空</notempty>
可以把上面两个标签合并成为:
<empty name=”name”>name为空<else/> name不为空</empty>

***7.15Defined标签
可以使用defined标签判断常量是否已经有定义,例如:
<defined name=”NAME”>NAME常量已经定义</defined>
如果判断没有被定义,可以使用:
<notdefined name=”NAME”>NAME常量未定义</notdefined>
可以把上面两个标签合并成为:
<defined name=”NAME”>NAME常量已经定义<else/> NAME常量未定义</defined>

***7.16IF标签
如果觉得上面的标签都无法满足条件判断要求的话,我们还可以使用if标签来定义复杂的条件判断,例如:
<if condition=”($name eq 1) OR ($name gt 100) “> value1
<elseif condition=”$name eq 2″ />value2
<else /> value3
</if>
在condition属性中可以支持eq等判断表达式 ,同上面的比较标签,但是不支持带有”>”、”<”等符号的用法,因为会混淆模板解析,所以下面的用法是错误的:
<if condition=”$id < 5 “> value1
<else /> value2
</if>
必须改成:
<if condition=”$id lt 5 “> value1
<else /> value2
</if>
除此之外,我们可以在condition属性里面使用php代码,例如:
<if condition=”strtoupper($user[‘name’]) neq ‘THINKPHP’ “> ThinkPHP
<else /> other Framework
</if>
condition属性可以支持点语法和对象语法,例如:
自动判断user变量是数组还是对象
<if condition=”$user.name neq ‘ThinkPHP’ “> ThinkPHP
<else /> other Framework
</if>
或者知道user变量是对象
<if condition=”$user:name neq ‘ThinkPHP’ “> ThinkPHP
<else /> other Framework
</if>
由于if标签的condition属性里面基本上使用的是php语法,尽可能使用判断标签和Switch标签会更加简洁,原则上来说,能够用switch和比较标签解决的尽量不用if标签完成。因为switch和比较标签可以使用变量调节器和系统变量。如果某些特殊的要求下面,IF标签仍然无法满足要求的话,可以使用原生php代码或者PHP标签来直接书写代码。

***7.17标签嵌套
模板引擎支持标签的多层嵌套功能,可以对标签库的标签指定可以嵌套。
系统内置的标签中,volist(及其别名iterate)、switch、if、elseif、else、foreach、compare(包括所有的比较标签)、(not)present、(not)empty、(not)defined等标签都可以嵌套使用。例如:
<volist name=”list” id=”vo”>
<volist name=”vo[‘sub’]” id=”sub”>
{$sub.name}
</volist>
</volist>
上面的标签可以用于输出双重循环。
默认的嵌套层次是3级,所以嵌套层次不能超过3层,如果需要更多的层次可以指定TAG_NESTED_LEVEL配置参数。

***7.18使用PHP代码
Php代码可以和标签在模板文件中混合使用,可以在模板文件里面书写任意的PHP语句代码 ,包括下面两种方式:
第一种是使用php标签:
<php>echo ‘Hello,world!';</php>
第二种就是直接使用原始的php代码:
<?phpecho ‘Hello,world!'; ?>
但是php标签或者php代码里面就不能再使用标签(包括普通标签和XML标签)了,因此下面的几种方式都是无效的:
<php><eq name=’name’value=’value’>value</eq></php>
Php标签里面使用了eq标签,因此无效
<php>if( {$user} != ‘ThinkPHP’ ) echo  ‘ThinkPHP’ ;</php>
Php标签里面使用了{$user}普通标签输出变量 ,因此无效。
<php>if( $user.name != ‘ThinkPHP’ ) echo  ‘ThinkPHP’ ;</php>
Php标签里面使用了$user.name变量输出 ,因此无效。
简而言之,在PHP标签里面不能再使用PHP本身不支持的代码。

***7.19原样输出
可以使用literal标签来防止模板标签被解析,例如:
<literal>
<if condition=”$name eq 1 “> value1
<elseif condition=”$name eq 2″/>value2
<else /> value3
</if>
</literal>
上面的if标签被literal标签包含,因此if标签里面的内容并不会被模板引擎解析,而是保持原样输出。
Literal标签可以用于页面的JS代码外面,确保JS代码中的某些用法和模板引擎不产生混淆。

***7.20模板注释
模板支持注释功能,该注释文字在最终页面不会显示,仅供模板制作人员参考和识别。
格式:{/*注释内容 */ }或 {//注释内容 }
说明:在显示页面的时候不会显示模板注释,仅供模板制作的时候参考。
注意{和注释标记之间不能有空格。
例如:
{// 这是模板注释内容 }
{/* 这是模板
注释内容*/ }
模板注释支持多行可以。模板注释在生成编译缓存文件后会自动删除,这一点和Html的注释不同。

***7.21引入标签库
前面我们所讲述的标签用法都是内置的标签库或者内置模板的用法,事实上,内置模板引擎的标签库是可以无限扩展和增加标签的,一旦你扩展和使用了新的标签库,就必须要告诉模板当前要使用的标签库名称,否则不会自动导入,防止以后标签库大量扩展后增加解析工作量,导入标签库使用tagLib标签。
格式:<tagLib name=”标签库1[,标签库2,…]”/>
可以同时导入多个标签库,用逗号分隔。
例如:
<tagLib name=”html”/>
表示在当前模板文件需要引入html标签库。要引入标签库必须确保有Html标签库的定义文件和解析类库(如何扩展这种方式请参考前面的标签库扩展部分)。Cx标签库内置导入,无需使用taglib标签导入。
引入后,html标签库的所有标签在当前模板页面中都可以使用了。外部导入的标签库必须使用标签库前缀的xml标签,避免两个不同的标签库中存在同名的标签定义,例如(假设Html标签库中已经有定义select和link标签):
<html:select options=’name’ selected=’value’ />
<html:link href=’/path/to/common.js’ />
标签库使用的时候忽略大小写,因此下面的方式一样有效:
<HTML:LINK HREF=’/path/to/common.js’ />
 
如果你的每个模板页面都需要加载Html标签库的话,也可以通过配置直接预先加载Html标签库。
 ‘TAGLIB_PRE_LOAD’ => ‘html’ ,
如果有多个标签库需要预先加载的话,用逗号分隔。定义之后,每个模板页面都可以直接使用:
<html:select options=’name’ selected=’value’ />
而不需手动引入Html标签库。
 
假设你确信Html标签库无论在现在还是将来都不会和系统内置的标签库存在相同的标签,那么可以配置TAGLIB_BUILD_IN的值把Html标签库作为内置标签库引入,例如:
 ‘TAGLIB_BUILD_IN’ => ‘cx,html’ ,
这样,也无需在模板文件页面引入Html标签库了,并且可以不带前缀直接使用Html标签库的标签:
<select options=’name’ selected=’value’ />
注意,cx标签库是系统内置标签库,不能删除定义。

***7.22修改定界符
模板文件可以包含普通模板标签和XML模板标签,内置模板引擎的普通模板标签默认以{和 }作为开始和结束标识,并且在开始标记紧跟标签的定义,如果之间有空格或者换行则被视为非模板标签直接输出。
例如:{$name} {$vo.name} {$vo[‘name’]|strtoupper}都属于普通模板标签
要更改普遍模板的起始标签和结束标签,请使用下面的配置参数:
TMPL_L_DELIM  //模板引擎普通标签开始标记
TMPL_R_DELIM    //模板引擎普通标签结束标记
例如在项目配置文件中增加下面的配置:
‘TMPL_L_DELIM’=>'<{‘,
‘TMPL_R_DELIM’=>’}>’,
普通标签的定界符就被修改了,原来的
{$name} {$vo.name}
必须使用
<{$name}> <{$vo.name}>才能生效了。
 
普通模板标签主要用于模板变量输出、模板注释和公共模板包含。如果要使用其它功能,请使用XML模板标签,ThinkPHP包含了一个基于XML和TagLib技术的模板标签,包含了普通模板有的功能,并且有一些方面的增强和补充,更重要的一点是新的标签库模板技术更加具有扩展性。新的TagLib标签库具有命名空间功能,ThinkPHP框架内置了CX标签库。如果你觉得XML标签无法在正在使用的编辑器里面无法编辑,还可以更改XML标签库的起始和结束标签,请修改下面的配置参数:
TAGLIB_BEGIN    //标签库标签开始标签
TAGLIB_END    //标签库标签结束标记
例如在项目配置文件中增加下面的配置:
‘TAGLIB_BEGIN’=>'[‘,
‘TAGLIB_END’=>’]’,
原来的
<eq name=”name” value=”value”>相等<else/>不相等</eq>
就必须改成
[eq name=”name” value=”value”]相等[else/]不相等[/eq]
 
注意XML标签和普通标签的定界符不能冲突,否则会导致解析错误。
XML模板标签可以用于模板变量输出、文件包含、模板注释、条件控制、循环输出等功能,而且完全可以自己扩展功能。

***8附录
***8.1常量参考
***8.1.1预定义常量
URL_COMMON=0
普通模式 URL
URL_PATHINFO=1 
PATHINFO URL
URL_REWRITE=2 
REWRITE URL
URL_COMPAT=3 
兼容模式 URL
HAS_ONE=1  
HAS_ONE关联定义
BELONGS_TO=2 
BELONGS_TO关联定义
HAS_MANY=3 
HAS_MANY关联定义
MANY_TO_MANY=4  
MANY_TO_MANY关联定义

**8.1.2系统常量
__ROOT__ : 网站根目录地址
__APP__ : 当前项目(入口文件)地址
__URL__ : 当前模块地址
__ACTION__: 当前操作地址
__SELF__ : 当前 URL地址
__CURRENT__ : 当前模块的模板目录
ACTION_NAME: 当前操作名称
APP_PATH: 当前项目目录
APP_NAME: 当前项目名称
APP_TMPL_PATH: 项目模板目录
APP_PUBLIC_PATH:项目公共文件目录
CACHE_PATH: 项目模版缓存目录
CONFIG_PATH:项目配置文件目录
COMMON_PATH: 项目公共文件目录
DATA_PATH: 项目数据文件目录
GROUP_NAME:当前分组名称
HTML_PATH: 项目静态文件目录
IS_APACHE: 是否属于 Apache
IS_CGI:是否属于 CGI模式
IS_IIS:是否属于 IIS
IS_WIN:是否属于Windows环境
LANG_SET: 浏览器语言
LIB_PATH: 项目类库目录
LOG_PATH: 项目日志文件目录
LANG_PATH: 项目语言文件目录
MODULE_NAME:当前模块名称
MEMORY_LIMIT_ON: 是否有内存使用限制
MAGIC_QUOTES_GPC: MAGIC_QUOTES_GPC
TEMP_PATH :项目临时文件目录
TMPL_PATH: 项目模版目录
THINK_PATH: ThinkPHP系统目录
THINK_VERSION:ThinkPHP版本号
TEMPLATE_NAME:当前模版名称
TEMPLATE_PATH:当前模版路径
VENDOR_PATH: 第三方类库目录
WEB_PUBLIC_PATH:网站公共目录

***8.2配置参考
***8.2.1应用配置
APP_DEBUG =   false
是否开启应用调试模式,默认关闭
APP_PLUGIN_ON  =   false
是否开启应用插件机制
APP_GROUP_DEPR   =’.’
模块分组之间的分割符
APP_GROUP_LIST       = ”
项目模块分组列表,多个组之间用逗号分隔,例如 ‘Admin,Home’
APP_DOMAIN_DEPLOY    =  false
是否使用独立域名部署项目,只有在项目目录本身就是网站根目录的情况下开启
APP_AUTOLOAD_REG=false
是否开启SPL_AUTOLOAD_REGISTER,如果其他类库中使用了__autoload方法的话需要开启
APP_AUTOLOAD_PATH=’Think.Util.’
__autoLoad的搜索路径,当前项目的Model和Action类会自动加载,无需设置,注意搜索顺序
APP_FILE_CASE  =   false
是否严格检查文件的大小写 (在Windows平台下面有效)
APP_CONFIG_LIST=array(‘taglibs’,’routes’,’tags’,’htmls’,’modules’,’actions’)
项目扩展配置文件列表

***8.2.2URL配置
URL相关配置包括URL模式、路由等。
URL_DISPATCH_ON= true,
是否启用Dispatcher,如果关闭,只能使用传统方式的参数传值。
URL_MODEL= 1
URL模式: 0普通模式 1 PATHINFO 2 REWRITE 3兼容模式 当URL_DISPATCH_ON开启后有效
默认为PATHINFO模式,提供最好的用户体验和SEO支持
URL_PATHINFO_MODEL= 2
PATHINFO模式,默认采用智能模式
普通模式1参数没有顺序/m/module/a/action/id/1
智能模式2自动识别模块和操作/module/action/id/1/或者 /module,action,id,1/…
URL_PATHINFO_DEPR = ‘/’
PATHINFO参数之间分割号
URL_ROUTER_ON = true
是否开启URL路由
URL_HTML_SUFFIX=     ”
伪静态后缀设置,例如 .shtml
URL_CASE_INSENSITIVE = false
URL是否不区分大小写,默认区分大小写

***8.2.3日志配置
LOG_RECORD = false
是否记录网站日志,默认不记录日志
LOG_RECORD_LEVEL =   array(‘EMERG’,’ALERT’,’CRIT’,’ERR’)
允许记录的日志级别
LOG_FILE_SIZE = 2097152
日志文件大小限制, 针对文件方式的日志记录,超过会自动生成备份文件

***8.2.4错误配置
ERROR_MESSAGE= ‘您浏览的页面暂时发生了错误!请稍后再试~’
错误显示信息 非调试模式有效
ERROR_PAGE = ”
错误定向页面,需要填写完整的URL地址

***8.2.5数据库配置
DB_CHARSET= ‘utf8′
数据库编码,默认采用utf8
DB_DEPLOY_TYPE=0
数据库部署方式 :0集中式(单一服务器) 1分布式(主从服务器)
DB_RW_SEPARATE   =  false
数据库读写是否分离,分布式数据库方式下面有效
DB_FIELDS_CACHE   =  true
开启数据表字段缓存
DB_TYPE  =  ‘mysql’
数据库类型
DB_HOST   =  ‘localhost’
数据库服务器地址
DB_NAME  =  ”
数据库名称
DB_USER    =  ‘root’
数据库用户名
DB_PWD     =  ”
数据库 密码
DB_PORT    =  3306
数据库使用的端口
DB_PREFIX   =  ‘think_’
数据库的表前缀
DB_SUFFIX    =  ”
数据库的表后缀
DB_FIELDTYPE_CHECK  =   false
是否进行字段类型检查

***8.2.6静态缓存配置
HTML_FILE_SUFFIX=    ‘.shtml’
 默认静态文件后缀
HTML_CACHE_ON   =     false
 默认关闭静态缓存
HTML_CACHE_TIME=   60
静态缓存有效期
HTML_READ_TYPE=      1
静态缓存读取方式 0 readfile 1 redirect

***8.2.7数据缓存配置
DATA_CACHE_TYPE=    ‘File’
数据缓存类型 支持 File Db Apc Memcache Shmop Sqlite Xcache Apachenote Eaccelerator
DATA_CACHE_PATH   =   TEMP_PATH
缓存路径设置 (仅对File方式缓存有效)
DATA_CACHE_TIME=    -1
数据缓存有效期
DATA_CACHE_COMPRESS=       false
数据缓存是否压缩缓存
 DATA_CACHE_CHECK=      false
数据缓存是否校验缓存
DATA_CACHE_SUBDIR=      false
使用子目录缓存 (自动根据缓存标识的哈希创建子目录)
DATA_PATH_LEVEL  =   1
子目录缓存级别
***8.2.8运行时间配置
SHOW_RUN_TIME=false
运行时间显示
SHOW_ADV_TIME=false
显示详细的运行时间,SHOW_RUN_TIME开启后有效
SHOW_DB_TIMES=false
显示数据库读写次数
SHOW_CACHE_TIMES=false
显示缓存读写次数
SHOW_USE_MEM=false
显示内存开销
SHOW_PAGE_TRACE=   false
显示页面Trace信息 由Trace文件定义和Action操作赋值
SHOW_ERROR_MSG  =  true
发生错误时显示错误信息

***8.2.9模板配置
TMPL_DETECT_THEME  =   false
自动侦测模板主题
TMPL_TEMPLATE_SUFFIX=’.html’
默认模板文件后缀
TMPL_CACHFILE_SUFFIX=’.php’
默认模板缓存后缀
TMPL_PARSE_STRING=  ”
模板引擎要自动替换的字符串,必须是数组形式。例如array(‘__MYPATH__’=>Lib_PATH,…)
TMPL_ACTION_ERROR  =’Public:success’
错误跳转模板文件
TMPL_ACTION_SUCCESS =’Public:success’
成功跳转模板文件
TMPL_TRACE_FILE  =THINK_PATH.’/Tpl/PageTrace.tpl.php’
页面Trace的模板文件
TMPL_EXCEPTION_FILE =THINK_PATH.’/Tpl/ThinkException.tpl.php’
异常页面的模板文件
TMPL_ENGINE_TYPE=  ‘Think’
 默认模板引擎
以下设置仅对使用Think模板引擎有效
TMPL_DENY_FUNC_LIST=’echo,exit’
模板引擎禁用函数
TMPL_L_DELIM='{‘
模板引擎普通标签开始标记
TMPL_R_DELIM=’}’
模板引擎普通标签结束标记
TMPL_VAR_IDENTIFY   =   ‘array’
模板变量识别 留空自动判断 array数组 obj对象
TMPL_FILE_DEPR=’/’
模板文件MODULE_NAME与ACTION_NAME之间的分割符,只对项目分组部署有效
TMPL_STRIP_SPACE  =  false
是否去除模板文件里面的html空格与换行
TMPL_CACHE_ON=true
默认开启模板编译缓存 false的话每次都重新编译模板
TMPL_CACHE_TIME=    -1
 模板缓存有效期 -1永久 单位为秒
TAGLIB_BEGIN='<‘
标签库标签开始标记
TAGLIB_END=  ‘>’
标签库标签结束标记
TAGLIB_LOAD  =  true
是否使用内置标签库之外的其它标签库,默认进行自动检测
TAGLIB_BUILD_IN    =  ‘cx’
内置标签库名称 可以添加自己的标签库,多个使用逗号分隔
TAGLIB_PRE_LOAD  =   ”
预先加载的标签库,无需在每个模板使用taglib标签加载, 多个使用逗号分隔
TAG_NESTED_LEVEL=   3
标签嵌套级别
***8.2.10Cookie设置
COOKIE_EXPIRE     =     3600
Coodie有效期
COOKIE_DOMAIN   =     ”
Cookie有效域名
COOKIE_PATH=      ‘/’
Cookie路径
COOKIE_PREFIX=   ”
 Cookie前缀 避免冲突

***8.2.11令牌验证配置
TOKEN_ON   =   true
是否开启令牌验证
TOKEN_NAME  =   ‘__hash__’
 令牌验证的表单隐藏字段名称
TOKEN_TYPE    =    ‘md5′
令牌验证哈希规则

***8.2.12默认值配置
DEFAULT_APP    =   ‘@’
默认模型类所在的项目名称 @表示当前项目
DEFAULT_GROUP  =   ‘Home’
默认分组
DEFAULT_MODULE=      ‘Index’
默认模块名称
DEFAULT_ACTION =     ‘index’
默认操作名称
DEFAULT_THEME=’default’
默认模板主题名称
DEFAULT_LANG = ‘zh-cn’
默认语言
DEFAULT_TIMEZONE=’PRC’
默认时区
DEFAULT_AJAX_RETURN=  ‘JSON’
AJAX数据返回格式 JSON XML …
DEFAULT_CHARSET=    ‘utf-8′
默认页面输出编码

***8.2.13系统变量配置
下面这些变量配置主要用于URL的特殊传值,在项目中的URL和表单参数尽量不要与之冲突,否则容易造成错误。
VAR_PATHINFO =’s’
PATHINFO兼容模式获取变量例如 ?s=/module/action/id/1
VAR_GROUP = ‘g’
默认分组变量
VAR_ROUTE=’r’
默认路由获取变量
VAR_MODULE=’m’
默认模块获取变量
VAR_ACTION=’a’
 默认操作获取变量
VAR_PAGE=’p’
默认分页跳转变量
VAR_TEMPLATE       =’t’
 默认模板切换变量
VAR_LANGUAGE=’l’
默认语言切换变量
VAR_AJAX_SUBMIT=’ajax’
默认的AJAX提交变量

***8.2.14语言和时区
LANG_SWITCH_ON=     false
是否开启多语言功能,默认关闭
LANG_AUTO_DETECT     =   true
是否自动侦测浏览器语言

***8.3函数参考
***8.3.1系统函数库
系统函数库的方法无需导入,可以直接使用,下面按照字母排序列出每个函数及其参数。
A(name, app=’@’)
实例化Action, name表示Actin名称 app表示项目名,默认是当前项目
返回实例化后的Action对象,如果对应的Action类不存在则返回false
auto_charset(contents, from, to)
编码转换,把contents从from转换到to,contents支持字符串和数组
B(name)
调用行为 name表示行为名称
C(name=null,value=null)
获取和设置配置定义,获取已有的配置值 C(‘name’) 新增或者更改设置C(‘name’,’value’) 如果name的值是数组,表示批量赋值
cookie(name,value=”,option=null)
Cookie 设置、获取、清除 name
D(name=”,app=”)
实例化模型类,name表示模型的名称,如果留空,表示实例化空模型,app表示项目名 默认是当前项目。跨项目实例化的话,项目目录必须保证同级。返回实例化后的Model类,如果对应的模型类不存在,则抛出异常
debug_start(label=”)
区间调试开始,label表示区间的标签名,例如 debug_start(‘read’)
debug_end(label=”)
区间调试结束,label表示区间的标签名,必须和debug_start对应才能输出正确
dump(var, echo=true,label=null, strict=true)
浏览器友好的变量输出,var支持任何变量,echo表示是否需要输出,如果为否,则返回要显示的字符串。Strict表示是否输出详细信息,如果为否,使用print_r输出,如果为是,使用var_dump输出。Dump函数还支持xdebug扩展
F(name,value=”,path=DATA_PATH)
快速文件数据读取和保存 针对简单类型数据 字符串、数组,F方法不支持缓存有效期 name为缓存名称(也就是缓存文件名),value表示缓存值,如果为NULL表示删除缓存,留空表示获取缓存值,其他情况表示设置缓存,path表示缓存文件所在路径,默认是项目的数据目录
file_exists_case(filename)
区分大小写的文件存在判断,只在Windows环境下面有效,Linux环境本身就区分大小写
get_instance_of(name,method=”,args=array())
单例化某个类,同时可以传入类的方法和参数
halt (error)
 输出错误并中止执行
import(class,baseUrl = ”,ext=’.class.php’)
基类库或者应用类库导入,class表示要导入的类库,采用命名空间的方式,例如:import(‘Think.Util.Session’) baseUrl表示导入的基础路径,留空的话系统有默认的规则,除了Think、ORG和Com类库包位于系统基类库目录外,其他方式都位于项目类库目录,ext表示类库后缀,默认是.class.php
L(name=null,value=null)
获取和设置语言定义 name表示语言变量名,value表示语言定义值,留空为获取语言定义
M(name=”,class=’Model’)
快速实例化模型类,name表示模型名称,但无需创建具体模型类(需要有对应的数据表即可),class表示要实例化的类名,默认是Model类,也可以使用其他模型类,例如M(‘User’, ‘AdvModel’) 实例化高级模型类的用户模型
mk_dir(dir, mode = 0755)
循环创建目录,如果dir参数中存在未创建的多级目录,会自动依次创建,mode表示目录的权限
R(module,action,app=’@’)
远程调用模块的操作方法,module是模块名,action是操作名,app是项目名,默认是当前项目
require_cache(filename)
优化的require,可以替代require和require_once函数
redirect(url,time=0,msg=”)
URL重定向, url必须是一个完整的URL地址,time表示等待时间,单位为秒,msg表示等待的提示信息
S(name,value=”,expire=”,type=”)
缓存设置或者读取 name表示缓存名称,value表示缓存的值,如果留空则表示获取缓存值,如果为NULL表示删除缓存,否则表示设置缓存,expire表示有效期,单位为秒,type是使用的缓存类型,包括File、APC、Db、Memcache、Shmop、Eaccelerator、Sqlite和Xcache
tag(name,params=array())
执行某个标签的方法,用于应用扩展的标签执行
throw_exception(msg,type=’ThinkException’,code=0)
抛出异常,msg为错误信息,type为异常类型 code为异常代码
to_guid_string(mix)
根据PHP各种类型变量生成唯一标识号
U (url,params=array(),redirect=false,suffix=true)
根据当前URL配置生成URL地址 并支持跳转
url表示URL规则,支持
‘[项目://][路由@][分组名-模块/]操作’
和 ‘[项目://][路由@][分组名-模块/]操作?参数1=值1&参数2=值2′
params表示参数,必须使用数组传入
redirect表示是否需要跳转到生成的URL地址
suffix表示是否添加伪静态后缀,设置了伪静态后有效
vendor(class,baseUrl = ”,ext=’.php’)
导入第三方类库,用法同import,只是baseUrl的默认值位于项目的第三方类库目录,ext后缀默认为.php
W(name,data=array(),return=false)
输出Widget,name为Widget名称 data表示传入的参数,必须使用数组,return表示是否需要返回结果,默认直接输出
xml_encode(data,encoding=’utf-8′,root=”think”)
把数组转换成XML,用于内置的AJAX返回XML格式的数据

***8.3.2兼容函数库
兼容函数库会在PHP版本低于5.2.0的时候自动加载,并且可以直接调用
json_encode(data)
把PHP数据编码成JSON格式字符串,方便客户端JS调用
json_decode(json,assoc=false)
把JSON格式字符串解码成PHP数据格式 assoc表示返回关联数组
property_exists(class, property)
检查对象或类是否具有该属性

***8.3.3编译函数库
编译函数库会在系统内部的编译过程中自动加载,实际运行过程无法调用。
compile(filename,runtime=false)
编译文件,filename要编译的文件名,runtime表示是否需要替换预编译指令,返回编译后的字符串
strip_whitespace(content)
去除代码中的空白和注释
array_define(array)
根据数组生成常量定义,array是一个关联数组
build_runtime()
生成核心编译缓存
mkdirs(dirs,mode=0777)
批量创建目录 dirs表示目录列表数组 mode表示目录权限
build_app_dir()
创建项目目录结构,默认会生成项目目录结构,并自动生成空白的项目配置文件和测试Action类,该方法不会覆盖已经存在的目录或者文件,支持安全文件写入
check_runtime()
检查runtime运行目录,并检查下面的Cache(模板缓存)、Temp(数据缓存)、Data(数据目录)和Logs(日志文件)子目录,如果不存在则自动创建

***8.3.4扩展函数库
扩展函数库的方法不能直接使用,需要加载或者拷贝到项目函数库中才能使用。
加载扩展函数库,使用:
Load(‘extend’);
加载扩展函数库后,就可以调用其中的所有函数了。
get_client_ip()
获取客户端的IP地址
msubstr(str, start=0, length, charset=”utf-8″, suffix=true)
中文字符串截取
rand_string(len=6,type=”,addChars=”)
产生随机字串
type是随机类型,包括:
0字母 1数字 2大写字母 3小写字母 4中文 5混合(去掉了容易混淆的字符oOLl和数字01)
addChars附加的字符串
build_verify (length=4,mode=1)
创建随机验证码,mode参数用法和rand_string的type一致
byte_format(size, dec=2)
字节格式化 把字节数格式为 B K M G T描述的容易理解的大小
is_utf8(string)
检测字符串是否是utf8编码
highlight_code(str,show=false)
代码高亮
h(text, tags = null)
输出安全的Html代码
ubb(Text)
基本的UBB解析
build_count_rand (number,length=4,mode=1)
随机生成一组字符串
remove_xss(val)
移除Html代码中的XSS攻击
list_to_tree(list, pk=’id’,pid = ‘pid’,child = ‘_child’,root=0)
把查询的数据集转换成树形列表数组, list表示查询的数据集(数组),pk表示主键名,pid表示父键名,child表示子列表的名称,默认是_child,root表示跟节点的主键值
list_sort_by(list,field, sortby=’asc’)
对查询的数据集排序,list表示查询的结果数据集(数组),field表示要排序的字段名称,sortby表示排序类型,包括asc正向排序 desc逆向排序 nat自然排序,默认为asc
list_ search(list,condition)
在查询的数据集中搜索数据,list表示查询的结果数据集(数组),condition表示查询条件,支持支持下面的查询方式
数组方式如 array(‘var1’=>’value1′, ‘var2’=>’value2′)并且支持正则表达式array(‘name’=>’/[A-Z]/’)
URL方式如var1=value1&var2=value2
send_http_status(status)
发送http状态信息,status表示http状态值,例如302、404

***8.4类库参考
***8.4.1Think类
__set(name ,value)设置类的属性值,魔术方法
__get(name) 获取类的属性值,魔术方法
autoload(classname)自动加载方法,用于开启APP_AUTOLOAD_REG的情况下
instance(class,method=”)实例化类,静态方法

***8.4.2Action类
getActionName()获取当前Action的名称
isAjax()是否为AJAX请求
isPost()是否为POST请求
isGet()是否为GET请求
isPut()是否为PUT请求
isDelete()是否为DELETE请求
isHead()是否为HEAD请求
display(templateFile=”,charset=”,contentType=’text/html’)输出模板
templateFile模板文件
charset输出编码
contentType输出类型
fetch(templateFile=”,charset=”,contentType=’text/html’)获取模板输出内容
buildHtml(htmlfile=”,templateFile=”,charset=”,contentType=’text/html’)生成静态页面
assign(name,value=”)模板变量赋值
__set(name,value)模板变量赋值,魔术方法
get(name)获取模板变量
trace(name,value=”) Trace变量赋值
error(message,ajax=false)错误跳转
success(message,ajax=false)成功跳转
ajaxReturn(data,info=”,status=1,type=”) AJAX返回数据到客户端
redirect(url,params=array(),delay=0,msg=”) Action重定向

***8.4.3View类
assign(name,value=”)模板变量赋值
trace(name,value=”) Trace变量赋值
get(name)获取模板变量
display(templateFile=”,charset=”,contentType=’text/html’)输出模板
fetch(templateFile=”,charset=”,contentType=’text/html’)获取模板输出内容
buildHtml(htmlfile=”,templateFile=”,charset=”,contentType=’text/html’)生成静态页面

***8.4.4Model类
getModelName()获取当前Model的名称
getTableName()获取当前Model的数据表名称
switchModel(type,vars=array())动态切换模型
table()设置当前操作的数据表
field()设置要查询的数据字段
where()设置查询或者操作条件
data(data)设置数据对象
order(order)设置排序
limit(limit)查询限制
page(page)查询分页
join(join)进行JOIN查询
having(having)进行having查询
group(group)进行group查询
lock(lock)查询锁定
distinct(distinct)唯一性查询
count(field)记录统计
sum(field)总数查询
min(field)最小值查询
max(field)最大值查询
avg(field)平均值查询
_initialize()模型初始化方法
_facade(data)对保存到数据库的数据进行处理
_before_write(&data)写入数据前的回调方法 包括新增和更新
add(data=”,options=array())新增数据
_before_insert(&data,options)写入数据前的回调方法
_after_insert(data,options)写入数据后的回调方法
selectAdd(fields=”,table=”,options=array())通过Select方式添加记录
save(data=”,options=array())更新数据到数据库
_before_update(&data,options) 更新数据前的回调方法
_after_update(data,options) 更新成功后的回调方法
delete(options=array())删除数据
_after_delete(data,options)删除成功后的回调方法
select(options=array())查询数据集
_after_select(&resultSet,options)查询成功后的回调方法
findAll(options=array()) select方法的别名
_options_filter(&options)表达式过滤回调方法
find(options=array())查询数据
_after_find(&result,options)查询成功的回调方法
setField(field,value,condition=”)设置记录的某个字段值
setInc(field,condition=”,step=1)字段值增长
setDec(field,condition=”,step=1)字段值减少
getField(field,condition=”,sepa=’ ‘)获取某个字段值
create(data=”,type=”)创建数据对象
autoCheckToken(data)表单令牌验证
query(sql)执行原生SQL查询
execute(sql=”)执行原生SQL操作
startTrans()启动事务
commit()提交事务
rollback()事务回滚
getError()获取模型的错误信息
getDbError()获取数据库的错误信息
getLastInsID()获取最后执行的SQL语句
getPk()获取主键名称
getDbFields()获取数据表的字段信息
regex(value,rule)使用正则验证数据
setProperty(name,value)设置模型的属性值
 
高级模型类AdvModel
topN(count,options=array())  查询满足条件的前N个记录
getN(position=0,options=array()) 查询符合条件的第N条记录
 0表示第一条记录 -1表示最后一条记录
first(options=array())获取满足条件的第一条记录
last(options=array())获取满足条件的最后一条记录
returnResult(data,type=”) 返回指定的数据类型
setLazyInc(field,condition=”,step=1,lazyTime=0)字段值延迟增长
setLazyDec(field,condition=”,step=1,lazyTime=0)字段值延迟减少
addConnect(config,linkNum=NULL)增加数据库连接
delConnect(linkNum)删除数据库连接
closeConnect(linkNum)关闭数据库连接
switchConnect(linkNum,name=”)切换数据库连接
patchQuery(sql=array())批处理执行SQL语句
getPartitionTableName(data=array())得到分表的的数据表名

***8.4.5Log类
record(message,level=self::ERR,record=false)记录日志信息到内存
save(type=self::FILE,destination=”,extra=”)保存记录的日志信息
write(message,level=self::ERR,type=self::FILE,destination=”,extra=”)直接写入日志信息
 

***8.4.6Db类
getInstance()获取数据库实例,静态方法
addConnect(config,linkNum=null)添加数据库连接
switchConnect(linkNum)切换数据库连接
insert(data,options=array())新增数据
selectInsert(fields,table,options=array()) Select方式写入数据
update(data,options)更新数据
delete(options=array())删除数据
select(options=array())查询数据
getLastSql()获取最后执行的SQL语句
getError()获取错误信息
 
数据库驱动类库
connect(config=”,linkNum=0)连接数据库
free()释放查询结果
query(str)数据查询
execute(str)执行语句
startTrans()启动事务
commit()事务提交
rollback()事务回滚
getFields(tableName)获取数据表字段信息
getTables(dbName=”)获取数据库的表信息
close()关闭数据库
error()获取错误信息
escape_string(str)安全过滤

***8.4.7Cache类
getInstance()获得缓存实例,静态方法
connect(type=”,options=array())连接缓存
setOptions(name,value)设置缓存属性
getOptions(name)获取缓存设置
__get(name)获取缓存,魔术方法
__set(name,value)设置缓存,魔术方法
__unset(name)删除缓存,魔术方法
 
缓存驱动类
get(name)获取缓存
set(name,value,expire=”)设置缓存
rm(name)删除缓存

***8.4.8Cookie类
is_set(name)判断Cookie是否存在 静态方法
get(name)获取Cookie的值 静态方法
set(name,value,expire=”,path=”,domain=”)设置Cookie的值 静态方法
delete(name)删除Cookie的值 静态方法
clear()清空Cookie的值

***8.4.9Session类
get(name)获取Session值
getLocal(name)获取本地化Session的值
set(name, value)设置Session的值
setLocal(name, value)设置本地化Session的值
is_set(name)检查Session的值是否设置
is_setLocal(name)检查本地化Session的值是否已经设置
pause()暂停session
clear()清空Session
clearLocal()清空本地化Session
destroy()销毁Session
isExpired()检测Session是否过期
isIdle()检测Session是否闲置

***8.4.10Debug类
mark(name)标记调试位置,静态方法
useTime(start,end,decimals = 6)调试区间所用的时间,静态方法
useMemory(start,end)调试区间所用的内存,静态方法
getMemPeak(start,end)调试区间的内存占用峰值,静态方法
 

***8.5关于升级
从1.6RC1版本升级
1.6RC1版本升级到2.0版本需要做如下的改动:
1、配置参数改动(参考下载包中的配置对照表)
2、其他的可以参考代码重构部分进行适当的调整
从1.5版本升级
从1.5版本升级到2.0版本可以使用兼容模式运行,请按照下面的步骤进行升级:
1、下载官方发布的兼容模式扩展,解压后放入系统目录的Mode目录下面;
2、修改项目的入口文件,在加载ThinkPHP.php公共文件之前增加下面一行:
define(‘THINK_MODE’,’Compat’);  // 设置为兼容模式运行
3、然后,把原先系统基类库的ORG类库包放入新版的基类库目录中。
4、清除项目编译缓存文件~runtime.php和 ~app.php。
如果你的版本低于1.5,请首先按照官方的发布说明(http://thinkphp.cn/Blog/12)升级到1.5版本,然后再按照上面的说明升级到2.0版本。

***8.6代码重构
1.5版本通过兼容模式可以运行在新版下面,但是使用的核心仍然是1.5版本的核心。因为新版的内核是完全重构的,接口有所改变,所以如果需要完全迁移到新的2.0版本,只有通过代码重构的方式实现,下面是1.5的代码重构到2.0版本的相关建议:
 参数方式的数据查询改成连贯操作,例如:
$Model->findall(‘id>1′,’id,name’,’id desc’,’10,100′);
应该改成
$Model->where(‘id>1′)->order(‘id desc’)->field(‘id,name’)->limit(‘10,100′)->select();
 布局方法layout用display替换(参考布局模板部分内容);
 URL方法换成U方法(用法也需要改变 参考URL生成部分内容);
 部分查询方法需要继承高级模型类AdvModel才可以使用,包括topN getN first last;
 原来Model的文本字段和乐观锁功能需要继承AdvModel才可使用;
 Action类的redirect方法接口因为URL方法的改变也有所改变;
 Model类的addAll,deleteAll方法已经删除;
 Model类的getFields方法请改成getField;
 Model类的deleteBy动态方法删除;
 Model类的自动验证和自动完成定义的时间定义格式改变(参考自动验证和自动完成部分内容);
 视图模型和关联模型已经分离出Model类,需要另外继承(参考视图模型和关联模型部分内容);
 数据库延迟查询功能已经取消;
 RBAC也已经分离出核心,需要自己调用;
 模板的点语法默认是数组输出了;
 Session可以设置是否需要自动开启;
 系统函数库删除了一些核心不依赖的函数,移入了扩展函数库extend.php需要的话自行放入项目函数库中即可;
 import方法的匹配导入和子目录导入由于性能问题不再支持;
 浏览器防刷新功能已经删除(改由行为扩展提供);
 URL伪装功能已经删除;
 编码自动转换功能已经取消,请自行用auto_charset转换编码;
 模板文件中导入外部js和css不需要导入Html标签库;
 Html标签库已经作为扩展提供,不再内置;
模板引擎的sublist、resultset和subeach标签已经取消,分别使用volist和foreach替代;

***8.7开源应用
基于ThinkPHP最新版本的部分开源应用列表:
 ThinkSNS:开源SNS系统
 ThinkCMS:开源CMS系统
 ThinkHost:开源虚拟主机管理系统
 MiniBlog:开源迷你博客系统
 ThinkMyAdmin:开源MySQL管理工具
 yBlog:开源博客系统
 shuguangCMS:开源CMS系统
更多开源应用请关注官方网站。

***8.8典型案例
基于ThinkPHP的典型案例参考请访问官方网站:http://thinkphp.cn/Case/

此条目发表在more cms分类目录。将固定链接加入收藏夹。

发表评论

邮箱地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>