基础:
1. 基础概念
LAMP
LAMP是基于Linux,Apache,MySQL和PHP的开放资源网络开发平台。这个术语来自欧洲,在那里这些程序常用来作为一种标准开发环境。名字来源于每个程序的第一个字母。每个程序在所有权里都符合开放源代码标准:Linux是开放系统;Apache是最通用的网络服务器;MySQL是带有基于网络管理附加工具的关系数据库;PHP是流行的对象脚本语言,它包含了多数其它语言的优秀特征来使得它的网络开发更加有效。开发者在Windows操作系统下使用这些Linux环境里的工具称为使用WAMP。
虽然这些开放源代码程序本身并不是专门设计成同另外几个程序一起工作的,但由于它们都是影响较大的开源软件,拥有很多共同特点,这就导致了这些组件经常在一起使用。在过去的几年里,这些组件的兼容性不断完善,在一起的应用情形变得更加普遍。并且它们为了改善不同组件之间的协作,已经创建了某些扩展功能。目前,几乎在所有的Linux发布版中都默认包含了这些产品。Linux操作系统、Apache服务器、MySQL数据库和Perl、PHP或者 Python语言,这些产品共同组成了一个强大的Web应用程序平台。
随着开源潮流的蓬勃发展,开放源代码的LAMP已经与J2EE和.Net商业软件形成三足鼎立之势,并且该软件开发的项目在软件方面的投资成本较低,因此受到整个IT界的关注。从网站的流量上来说,70%以上的访问流量是LAMP来提供的,LAMP是最强大的网站解决方案.
OOP
面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)是一种计算机编程架构。OOP 的一条基本原则是计算机程序是由单个能够起到子程序作用的单元或对象组合而成。OOP 达到了软件工程的三个主要目标:重用性、灵活性和扩展性。为了实现整体运算,每个对象都能够接收信息、处理数据和向其它对象发送信息。OOP 主要有以下的概念和组件:
组件 - 数据和功能一起在运行着的计算机程序中形成的单元,组件在 OOP 计算机程序中是模块和结构化的基础。
抽象性 - 程序有能力忽略正在处理中信息的某些方面,即对信息主要方面关注的能力。
封装 - 也叫做信息封装:确保组件不会以不可预期的方式改变其它组件的内部状态;只有在那些提供了内部状态改变方法的组件中,才可以访问其内部状态。每类组件都提供了一个与其它组件联系的接口,并规定了其它组件进行调用的方法。
多态性 - 组件的引用和类集会涉及到其它许多不同类型的组件,而且引用组件所产生的结果得依据实际调用的类型。
继承性 - 允许在现存的组件基础上创建子类组件,这统一并增强了多态性和封装性。典型地来说就是用类来对组件进行分组,而且还可以定义新类为现存的类的扩展,这样就可以将类组织成树形或网状结构,这体现了动作的通用性。
由于抽象性、封装性、重用性以及便于使用等方面的原因,以组件为基础的编程在脚本语言中已经变得特别流行。
MVC
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的处理过程,首先控制器接收用户的请求,并决定应该调用哪个模型来进行处理,然后模型用业务逻辑来处理用户的请求并返回数据,最后控制器用相应的视图格式化模型返回的数据,并通过表示层呈现给用户。
ORM
对象-关系映射(Object/Relation Mapping,简称ORM),是随着面向对象的软件开发方法发展而产生的。面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射(ORM)系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。
面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为了解决这个不匹配的现象,对象关系映射技术应运而生。
AOP
AOP(Aspect-Oriented Programming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。而AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。所谓“方面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
CURD
CURD是一个数据库技术中的缩写词,一般的项目开发的各种参数的基本功能都是CURD。它代表创建(Create)、更新(Update)、读取(Read)和删除(Delete)操作。CURD 定义了用于处理数据的基本原子操作。之所以将CURD 提升到一个技术难题的高度是因为完成一个涉及在多个数据库系统中进行CURD操作的汇总相关的活动,其性能可能会随数据关系的变化而有非常大的差异。
CURD在具体的应用中并非一定使用create、update 、read和delete字样的方法,但是他们完成的功能是一致的。例如,ThinkPHP就是使用add、save、select和delete方法表示模型的CURD操作。
ActiveRecord
Active Record(中文名:活动记录)是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。Active Record 和 Row Gateway (行记录入口)十分相似,但前者是领域模型,后者是一种数据源模式。关系型数据库往往通过外键来表述实体关系,Active Record 在数据源层面上也将这种关系映射为对象的关联和聚集。 Active Record 适合非常简单的领域需求,尤其在领域模型和数据库模型十分相似的情况下。如果遇到更加复杂的领域模型结构(例如用到继承、策略的领域模型),往往需要使用分离数据源的领域模型,结合 Data Mapper (数据映射器)使用。
Active Record 驱动框架一般兼有 ORM 框架的功能,但 Active Record 不是简单的 ORM,正如和 Row Gateway 的区别。由Rails最早提出,遵循标准的ORM模型:表映射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而且简洁易懂。
单一入口
单一入口通常是指一个项目或者应用具有一个统一(但并不一定是唯一)的入口文件,也就是说项目的所有功能操作都是通过这个入口文件进行的,并且往往入口文件是第一步被执行的。
单一入口的好处是项目整体比较规范,因为同一个入口,往往其不同操作之间具有相同的规则。另外一个方面就是单一入口带来的好处是控制较为灵活,因为拦截方便了,类似如一些权限控制、用户登录方面的判断和操作可以统一处理了。
或者有些人会担心所有网站都通过一个入口文件进行访问,是否会造成太大的压力,其实这是杞人忧天的想法。
2. 目录结构
目录/文件 | 说明 |
---|---|
ThinkPHP.php | 框架入口文件 |
Common | 框架公共文件目录 |
Conf | 框架配置文件目录 |
Lang | 框架系统语言目录 |
Lib | 系统核心基类库目录 |
Tpl | 系统模板目录 |
Extend | 框架扩展目录(关于扩展目录的详细信息请参考后面的扩展章节) |
注意:如果你下载的是核心版本,有可能Extend目录是空的,因为ThinkPHP本身不依赖任何扩展。
3. 命名规范
使用ThinkPHP开发的过程中应该尽量遵循下列命名规范:
- 类文件都是以.class.php为后缀(这里是指的ThinkPHP内部使用的类库文件,不代表外部加载的类库文件),使用驼峰法命名,并且首字母大写,例如DbMysql.class.php;
- 确保文件的命名和调用大小写一致,是由于在类Unix系统上面,对大小写是敏感的(而ThinkPHP在调试模式下面,即使在Windows平台也会严格检查大小写);
- 类名和文件名一致(包括上面说的大小写一致),例如 UserAction类的文件命名是UserAction.class.php, InfoModel类的文件名是InfoModel.class.php, 并且不同的类库的类命名有一定的规范;
- 函数、配置文件等其他类库文件之外的一般是以.php为后缀(第三方引入的不做要求);
- 函数的命名使用小写字母和下划线的方式,例如 get_client_ip;
- 方法的命名使用驼峰法,并且首字母小写或者使用下划线“_”,例如 getUserName,_parseType,通常下划线开头的方法属于私有方法;
- 属性的命名使用驼峰法,并且首字母小写或者使用下划线“_”,例如 tableName、_instance,通常下划线开头的属性属于私有属性;
- 以双下划线“__”打头的函数或方法作为魔法方法,例如 __call 和 __autoload;
- 常量以大写字母和下划线命名,例如 HAS_ONE和 MANY_TO_MANY;
- 配置参数以大写字母和下划线命名,例如HTML_CACHE_ON;
- 语言变量以大写字母和下划线命名,例如MY_LANG,以下划线打头的语言变量通常用于系统语言变量,例如 _CLASS_NOT_EXIST_;
- 对变量的命名没有强制的规范,可以根据团队规范来进行;
- ThinkPHP的模板文件默认是以.html 为后缀(可以通过配置修改);
- 数据表和字段采用小写加下划线方式命名,并注意字段名不要以下划线开头,例如 think_user 表和 user_name字段,类似 _username 这样的数据表字段可能会被过滤。
在ThinkPHP里面,有一个函数命名的特例,就是单字母大写函数,这类函数通常是某些操作的快捷定义,或者有特殊的作用。例如,ADSL方法等等。
另外有一点非常关键,ThinkPHP默认全部使用UTF-8编码,所以请确保你的程序文件采用UTF-8编码格式保存,并且去掉BOM信息头(去掉BOM头信息有很多方式,不同的编辑器都有设置方法,也可以用工具进行统一检测和处理),否则可能导致很多意想不到的问题。
4. CBD架构
ThinkPHP3.0版本引入了全新的CBD(核心Core+行为Behavior+驱动Driver)架构模式,因为从底层开始,框架就采用核心+行为+驱动的架构体系,核心保留了最关键的部分,并在重要位置设置了标签用以标记,其他功能都采用行为扩展和驱动的方式组合,开发人员可以根据自己的需要,对某个标签位置进行行为扩展或者替换,就可以方便的定制框架底层,也可以在应用层添加自己的标签位置和添加应用行。而标签位置类似于AOP概念中的“切面”,行为都是围绕这个“切面”来进行编程,如果把系统内置的核心扩展看成是一种标准模式的话,那么用户可以把这一切的行为定制打包成一个新的模式,所以在ThinkPHP里面,称之为模式扩展,事实上,模式扩展不仅仅可以替换和增加行为,还可以对底层的MVC进行替换和修改,以达到量身定制的目的。利用这一新的特性,开发人员可以方便地通过模式扩展为自己量身定制一套属于自己或者企业的开发框架,新版的模式扩展是框架扩展的集大成者,开创了新的里程碑,这正是新版的真正魅力所在。
5. 开发流程
使用ThinkPHP创建应用的一般开发流程是:
- 系统设计、创建数据库和数据表;(可选)
- 项目命名并创建项目入口文件,开启调试模式;
- 完成项目配置;
- 创建项目函数库;(可选)
- 开发项目需要的扩展(模式、驱动、标签库等);(可选)
- 创建控制器类;
- 创建模型类;(可选)
- 创建模板文件;
- 运行和调试、分析日志;
- 开发和设置缓存功能;(可选)
- 添加路由支持;(可选)
- 安全检查;(可选 )
- 部署到生产环境。
6. 入口文件
ThinkPHP采用单一入口模式进行项目部署和访问,无论完成什么功能,一个项目都有一个统一(但不一定是唯一)的入口。应该说,所有项目都是从入口文件开始的,并且所有的项目的入口文件是类似的,入口文件中主要包括:
- 定义框架路径、项目路径和项目名称(可选)
- 定义调试模式和运行模式的相关常量(可选)
- 载入框架入口文件(必须)
7. 项目目录
生成的项目目录结构和系统目录类似,包括:
目录 | 说明 |
---|---|
Common | 项目公共文件目录,一般放置项目的公共函数 |
Conf | 项目配置目录,项目所有的配置文件都放在这里 |
Lang | 项目语言包目录(可选 如果不需要多语言支持 可删除) |
Lib | 项目类库目录,通常包括Action和Model子目录 |
Tpl | 项目模板目录,支持模板主题 |
Runtime | 项目运行时目录,包括Cache(模板缓存)、Temp(数据缓存)、Data(数据目录)和Logs(日志文件)子目录,如果存在分组的话,则首先是分组目录。 |
如果需要把index.php 移动到App目录的外面,只需要在入口文件中增加项目名称和项目路径定义。
- <?php
- //定义项目名称
- define(‘APP_NAME‘, ‘App‘);
- //定义项目路径
- define(‘APP_PATH‘, ‘./App/‘);
- //加载框架入文件
- require ‘./App/ThinkPHP/ThinkPHP.php‘;
APP_NAME 是指项目名称,注意APP_NAME 不要随意设置,通常是项目的目录名称,如果你的项目是直接部署在Web根目录下面的话,那么需要设置APP_NAME 为空。
APP_PATH 是指项目路径(必须以“/”结束),项目路径是指项目的Common、Lib目录所在的位置,而不是项目入口文件所在的位置。
注意:在类Unix或者Linux环境下面Runtime目录需要可写权限。
8. 部署目录
目录/文件 | 说明 |
---|---|
ThinkPHP | 系统目录(下面的目录结构同上面的系统目录) |
Public | 网站公共资源目录(存放网站的Css、Js和图片等资源) |
Uploads | 网站上传目录(用户上传的统一目录) |
Home | 项目目录(下面的目录结构同上面的应用目录) |
Admin | 后台管理项目目录 |
…… 更多的项目目录 | |
index.php | 项目Home的入口文件 |
admin.php | 项目Admin的入口文件 |
…… 更多的项目入口文件 |
项目的模板文件还是放到项目的Tpl目录下面,只是将外部调用的资源文件, 包括图片 JS 和CSS统一放到网站的公共目录Public下面,分Images、Js和Css子目录存放,如果有可能的话,甚至也可以把这些资源文件单独放一个外部的服务器远程调用,并进行优化。
事实上,系统目录和项目目录可以放到非WEB访问目录下面,网站目录下面只需要放置Public公共目录和入口文件,从而提高网站的安全性。
如果希望自己设置目录,可以在入口文件里面更改RUNTIME_PATH常量进行更改,例如:
- define(‘RUNTIME_PATH‘,‘./App/temp/‘);
注意RUNTIME_PATH目录必须设置为可写权限。
除了自定义编译缓存目录之外,还支持自定义编译缓存文件名,例如:
- define(‘RUNTIME_FILE‘,‘./App/temp/runtime_cache.php‘);
ThinkPHP框架中所有配置文件的定义格式均采用返回PHP数组的方式,格式为:
- //项目配置文件
- return array(
- ‘DEFAULT_MODULE‘ => ‘Index‘, //默认模块
- ‘URL_MODEL‘ => ‘2‘, //URL模式
- ‘SESSION_AUTO_START‘ => true, //是否开启session
- //更多配置参数
- //...
- );
配置参数不区分大小写(因为无论大小写定义都会转换成小写)
还可以在配置文件中可以使用二维数组来配置更多的信息,例如:
- //项目配置文件
- return array(
- ‘DEFAULT_MODULE‘ => ‘Index‘, //默认模块
- ‘URL_MODEL‘ => ‘2‘, //URL模式
- ‘SESSION_AUTO_START‘ => true, //是否开启session
- ‘USER_CONFIG‘ => array(
- ‘USER_AUTH‘ => true,
- ‘USER_TYPE‘ => 2,
- ),
- //更多配置参数
- //...
- );
需要注意的是,二级参数配置区分大小写,也就说读取确保和定义一致。
9. 惯例配置和项目配置,调试配置
惯例重于配置是系统遵循的一个重要思想,系统内置有一个惯例配置文件(位于系统目录下面的Conf\convention.php),按照大多数的使用对常用参数进行了默认配置。
项目配置文件是最常用的配置文件,项目配置文件位于项目的配置文件目录Conf下面,文件名是config.php。
在项目配置文件里面除了添加内置的参数配置外,还可以额外添加项目需要的配置参数。
如果没有配置应用状态,系统默认则默认为debug状态,也就是说默认的配置参数是:
- ‘APP_STATUS‘ => ‘debug‘, //应用调试模式状态
debug.php配置文件只需要配置和项目配置文件以及系统调试配置文件不同的参数或者新增的参数。
如果想在调试模式下面增加应用状态,例如测试状态,则可以在项目配置文件中改变设置如下:
- ‘APP_STATUS‘ => ‘test‘, //应用调试模式状态
由于调试模式没有任何缓存,因此涉及到较多的文件IO操作和模板实时编译,所以在开启调试模式的情况下,性能会有一定的下降,但不会影响部署模式的性能。
注意:一旦关闭调试模式,项目的调试配置文件即刻失效。
10. 分组配置和读取配置,动态配置
如果启用了模块分组,则可以在对每个分组单独定义配置文件,分组配置文件位于:
项目配置目录/分组名称/config.php
可以通过如下配置启用分组:
- ‘APP_GROUP_LIST‘ => ‘Home,Admin‘, //项目分组设定
- ‘DEFAULT_GROUP‘ => ‘Home‘, //默认分组
现在定义了Home和Admin两个分组,则我们可以定义分组配置文件如下:
Conf/Home/config.php
Conf/Admin/config.php
每个分组的配置文件仅在当前分组有效,分组配置的定义格式和项目配置是一样的。
注意:分组名称区分大小写,必须和定义的分组名一致。
定义了配置文件之后,可以使用系统提供的C方法(如果觉得比较奇怪的话,可以借助Config单词来帮助记忆)来读取已有的配置:
- C(‘参数名称‘)//获取已经设置的参数值
例如,C(‘APP_STATUS‘) 可以读取到系统的调试模式的设置值,如果APP_STATUS尚未存在设置,则返回NULL。
C方法同样可以用于读取二维配置:
- C(‘USER_CONFIG.USER_TYPE‘)//获取用户配置中的用户类型设置
因为配置参数是全局有效的,因此C方法可以在任何地方读取任何配置,哪怕某个设置参数已经生效过期了。
在具体的Action方法里面,我们仍然可以对某些参数进行动态配置,主要是指那些还没有被使用的参数。
设置新的值:
- C(‘参数名称‘,‘新的参数值‘);
例如,我们需要动态改变数据缓存的有效期的话,可以使用
- C(‘DATA_CACHE_TIME‘,‘60‘);
也可以支持二维数组的读取和设置,使用点语法进行操作,如下:
获取已经设置的参数值:
- C(‘USER_CONFIG.USER_TYPE‘);
设置新的值:
- C(‘USER_CONFIG.USER_TYPE‘,‘1‘);
3.1版本开始,C函数支持配置保存功能,仅对批量设置有效,使用方法:
- C($array,‘name‘);
其中array是一个数组变量,会把批量设置后的配置参数列表保存到name标识的缓存数据中
获取缓存的设置列表数据 可以用
- C(‘‘,‘name‘); //或者C(null,‘name‘);
会读取name标识的缓存配置数据到当前配置数据(合并)。
11. 扩展配置
项目配置文件在部署模式的时候会纳入编译缓存,也就是说编译后再修改项目配置文件就不会立刻生效,需要删除编译缓存后才能生效。扩展配置文件则不受此限制影响,即使在部署模式下面,修改配置后可以实时生效,并且配置格式和项目配置一样。
设置扩展配置的方式如下(多个文件用逗号分隔):
- ‘LOAD_EXT_CONFIG‘ => ‘user,db‘, // 加载扩展配置文件
项目设置了加载扩展配置文件user.php 和db.php分别用于用户配置和数据库配置,那么会自动加载项目配置目录下面的配置文件Conf/user.php和Conf/db.php。
如果希望采用二级配置方式,可以设置如下:
- ‘LOAD_EXT_CONFIG‘ => array(
- ‘USER‘ => ‘user‘, //用户配置
- ‘DB‘ => ‘db‘, //数据库配置
- ), //加载扩展配置文件
同样的user.php 配置文件内容,但最终获取用户参数的方式就变成了:
- C(‘USER.USER_AUTH_ID‘);
这种方式可以避免大项目情况中的参数冲突问题。
下面的一些配置文件已经被系统使用,请不要作为自定义的扩展配置重新定义:
文件名 | 说明 |
---|---|
config.php | 项目配置文件 |
tags.php | 项目行为配置文件 |
alias.php | 项目别名定义文件 |
debug.php | 项目调试模式配置文件(以及项目设置的APP_STATUS对应的配置文件) |
core.php | 项目追加的核心编译列表文件(不会覆盖核心编译列表) |
12. 函数库
ThinkPHP中的函数库可以分为系统函数库和项目函数库。
系统函数库
库系统函数库位于系统的Common目录下面,有三个文件:
common.php是全局必须加载的基础函数库,在任何时候都可以直接调用;
functions.php是框架标准模式的公共函数库,其他模式可以替换加载自己的公共函数库或者对公共函数库中的函数进行重新定义;
runtime.php是框架运行时文件,仅在调试模式或者编译过程才会被加载,因此其中的方法在项目中不能直接调用;
项目函数库
库项目函数库通常位于项目的Common目录下面,文件名为common.php,该文件会在执行过程中自动加载,并且合并到项目编译统一缓存,如果使用了分组部署方式,并且该目录下存在"分组名称/function.php"文件,也会根据当前分组执行时对应进行自动加载,因此项目函数库的所有函数也都可以无需手动载入而直接使用。
如果项目配置中使用了动态函数加载配置的话,项目Common目录下面可能会存在更多的函数文件,动态加载的函数文件不会纳入编译缓存。
在特殊的情况下,模式可以改变自动加载的项目函数库的位置或者名称。
扩展函数库
库我们可以在项目公共目录下面定义扩展函数库,方便需要的时候加载和调用。扩展函数库的函数定义规范和项目函数库一致,只是函数库文件名可以随意命名,一般来说,扩展函数库并不会自动加载,除非你设置了动态载入。
函数加载
系统函数库和项目函数库中的函数无需加载就可以直接调用,对于项目的扩展函数库,可以采用下面两种方式调用:
动态载入
我们可以在项目配置文件中定义LOAD_EXT_FILE参数,例如:
- "LOAD_EXT_FILE"=>"user,db"
通过上面的设置,就会执行过程中自动载入项目公共目录下面的扩展函数库文件user.php和db.php,这样就可以直接在项目中调用扩展函数库user.php和db.php中的函数了,而且扩展函数库的函数修改是实时生效的。
手动载入
如果你的函数只是个别模块偶尔使用,则不需要采用自动加载方式,可以在需要调用的时候采用load方法手动载入,方式如下:
- load("@.user")
@.user表示加载当前项目的user函数文件,这样就可以直接user.php扩展函数库中的函数了。
13. 类库
ThinkPHP的类库包括基类库和应用类库,系统的类库命名规则如下:
类库 | 规则 | 示例 |
---|---|---|
控制器类 | 模块名+Action | 例如 UserAction、InfoAction |
模型类 | 模型名+Model | 例如 UserModel、InfoModel |
行为类 | 行为名+Behavior | 例如CheckRouteBehavior |
Widget类 | Widget名+Widget | 例如BlogInfoWidget |
驱动类 | 引擎名+驱动名 | 例如DbMysql表示mysql数据库驱动、CacheFile表示文件缓存驱动 |
基类库
基类库是指符合ThinkPHP类库规范的系统类库,包括ThinkPHP的核心基类库和扩展基类库。核心基类库目录位于系统的Lib目录,核心基类库也就是Think类库,扩展基类库位于Extend/Library目录,可以扩展ORG 、Com扩展类库。核心基类库的作用是完成框架的通用性开发而必须的基础类和内置支持类等,包含有:
目录 | 调用路径 | 说明 |
---|---|---|
Lib/Core | Think.Core | 核心类库包 |
Lib/Behavior | Think.Behavior | 内置行为类库包 |
Lib/Driver | Think.Driver | 内置驱动类库包 |
Lib/Template | Think.Template | 内置模板引擎类库包 |
核心类库包下面包含下面核心类库:
类名 | 说明 |
---|---|
Action | 系统基础控制器类 |
App | 系统应用类 |
Behavior | 系统行为基础类 |
Cache | 系统缓存类 |
Db | 系统抽象数据库类 |
Dispatcher | URL调度类 |
Log | 系统日志类 |
Model | 系统基础模型类 |
Think | 系统入口和静态类 |
ThinkException | 系统基础异常类 |
View | 视图类 |
Widget | 系统Widget基础类 |
应用类库
应用类库是指项目中自己定义或者使用的类库,这些类库也是遵循ThinkPHP的命名规范。应用类库目录位于项目目录下面的Lib目录。应用类库的范围很广,包括Action类库、Model类库或者其他的工具类库,通常包括:
目录 | 调用路径 | 说明 |
---|---|---|
Lib/Action | @.Action或自动加载 | 控制器类库包 |
Lib/Model | @.Model或自动加载 | 模型类库包 |
Lib/Behavior | 用B方法调用或自动加载 | 应用行为类库包 |
Lib/Widget | 用W方法在模板中调用 | 应用Widget类库包 |
项目根据自己的需要可以在项目类库目录下面添加自己的类库包,例如Lib/Common、Lib/Tool等。
类库导入
一、Import显式导入
ThinkPHP模拟了Java的类库导入机制,统一采用import方法进行类文件的加载。import方法是ThinkPHP内建的类库导入方法,提供了方便和灵活的文件导入机制,完全可以替代PHP的require和include方法。例如:
- import("Think.Util.Session");
- import("App.Model.UserModel");
import方法具有缓存和检测机制,相同的文件不会重复导入,如果导入了不同的位置下面的同名类库文件,系统也不会再次导入
注意:在Unix或者Linux主机下面是区别大小写的,所以在使用import方法的时候要注意目录名和类库名称的大小写,否则会导入失败。对于import方法,系统会自动识别导入类库文件的位置,ThinkPHP的约定是Think、ORG、Com包的导入作为基类库导入,否则就认为是项目应用类库导入。
- import("Think.Util.Session");
- import("ORG.Util.Page");
上面两个方法分别导入了Think基类库的Util/Session.class.php文件和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方法还可以支持别名导入,要使用别名导入,首先要定义别名,我们可以在项目配置目录下面增加alias.php 用以定义项目中需要用到的类库别名,例如:
- return array(
- ‘rbac‘ =>LIB_PATH.‘Common/Rbac.class.php‘,
- ‘page‘ =>LIB_PATH.‘Common/Page.class.php‘,
- );
那么,现在就可以直接使用:
- import("rbac");
- import("page");
导入Rbac和Page类,别名导入方式禁止使用import方法的第二和第三个参数,别名导入方式的效率比命名空间导入方式要高效,缺点是需要预先定义相关别名。
导入第三方类库
第三方类库统一放置在系统扩展目录下的Vendor 目录,并且使用vendor 方法导入,其参数和 import 方法是 一致的,只是默认的值有针对变化。 例如,我们把 Zend 的 Filter\Dir.php 放到 Vendor 目录下面,这个时候 Dir 文件的路径就是
Vendor\Zend\Filter\Dir.php,我们使用vendor 方法导入只需要使用:
- Vendor(‘Zend.Filter.Dir‘);
就可以导入Dir类库了。
Vendor方法也可以支持和import方法一样的基础路径和文件名后缀参数,例如:
- Vendor(‘Zend.Filter.Dir‘,dirname(__FILE__),‘.class.php‘);
自动加载
在大多数情况下,我们无需手动导入类库,而是通过配置采用自动加载机制即可,自动加载机制是真正的按需加载,可以很大程度的提高性能。自动加载有三种情况,按照加载优先级从高到低分别是:别名自动加载、系统规则自动加载和自定义路径自动加载。
一、别名自动加载
在前面我们提到了别名的定义方式,并且采用了import方法进行别名导入,其实所有定义别名的类库都无需再手动加载,系统会按需自动加载。
二、 系统规则自动加载
果你没有定义别名的话,系统会首先按照内置的规则来判断加载,系统规则仅针对行为类、模型类和控制器类,搜索规则如下:
类名 | 规则 | 说明 |
---|---|---|
行为类 | 规则1 | 搜索系统类库目录下面的Behavior目录 |
规则2 | 搜索系统扩展目录下面的Behavior目录 | |
规则3 | 搜索应用类库目录下面的Behavior目录 | |
规则4 | 如果启用了模式扩展,则搜索模式扩展目录下面的Behavior目录 | |
模型类 | 规则1 | 如果启用分组,则搜索应用类库目录的Model/当前分组 目录 |
规则2 | 搜索应用类库下面的Model目录 | |
规则3 | 搜索系统扩展目录下面的Model目录 | |
控制器类 | 规则1 | 如果启用分组,则搜索应用类库目录的Action/当前分组 目录 |
规则2 | 搜索项目类库目录下面的Action目录 | |
规则3 | 搜索系统扩展目录下面的Action目录 |
注意:搜索的优先顺序从上至下 ,一旦找到则返回,后面规则不再检测。如果全部规则检测完成后依然没有找到类库,则开始进行第三个自定义路径自动加载检测。
三、 自定义路径自动加载
当你的类库比较集中在某个目录下面,而且不想定义太多的别名导入的话,可以使用自定义路径自动加载方式,这种方式需要在项目配置文件中添加自动加载的搜索路径,例如:
- ‘APP_AUTOLOAD_PATH‘ =>‘@.Common,@.Tool‘,
表示,在当前项目类库目录下面的Common和Tool目录下面的类库可以自动加载。多个搜索路径之间用逗号分割,并且注意定义的顺序也就是自动搜索的顺序。
注意:自动搜索路径定义只能采用命名空间方式,也就是说这种方式只能自动加载项目类库目录和基类库目录下面的类库文件。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
控制器:
1. URL模式
传统方式的文件入口访问会变成由URL的参数来统一解析和调度。
ThinkPHP支持四种URL模式,可以通过设置URL_MODEL参数来定义,包括普通模式、PATHINFO、REWRITE和兼容模式。
一、普通模式:设置URL_MODEL 为0
采用传统的URL参数模式
- http://serverName/appName/?m=module&a=action&id=1
二、PATHINFO模式(默认模式):设置URL_MODEL 为1
默认情况使用PATHINFO模式,ThinkPHP内置强大的PATHINFO支持,提供灵活和友好URL支持。PATHINFO模式自动识别模块和操作,例如
- http://serverName/appName/module/action/id/1/或者
- http://serverName/appName/module,action,id,1/
三、REWRITE模式: 设置URL_MODEL 为2
该URL模式和PATHINFO模式功能一样,除了可以不需要在URL里面写入口文件,和可以定义.htaccess 文件外。在开启了Apache的URL_REWRITE模块后,就可以启用REWRITE模式了,具体参考下面的URL重写部分。
四、兼容模式: 设置URL_MODEL 为3
兼容模式是普通模式和PATHINFO模式的结合,并且可以让应用在需要的时候直接切换到PATHINFO模式而不需要更改模板和程序,还可以和URL_WRITE模式整合。兼容模式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模式部署即可,程序和模板都不需要做任何改动。
2. 模块和操作
http://域名/项目名/分组名/模块名/操作名/其他参数
Dispatcher会根据URL地址来获取当前需要执行的项目、分组(如果有定义的话)模块、操作以及其他参数,在某些情况下,项目名可能不会出现在URL地址中(通常情况下入口文件则代表了某个项目,而且入口文件可以被隐藏)。
每一个模块就是一个控制器类,通常位于项目的Lib\Action目录下面。
3.1版本开始,增加ACTION_SUFFIX配置参数,用于设置操作方法的后缀。
例如,如果设置:
- ‘ACTION_SUFFIX‘=>‘Act‘
那么访问某个模块的add操作对应读取模块类的操作方法则由原来的add方法变成addAct方法。
3. 定义控制器和空操作,空模块
一个应用如果不需要和数据库交互的时候可以不需要定义模型类,但是必须定义Action控制器,一般位于项目的Lib/Action目录下面。
Action控制器的定义非常简单,只要继承Action基础类就可以了,例如:
- Class UserAction extends Action{}
控制器文件的名称是UserAction.class.php。
空操作是指系统在找不到指定的操作方法的时候,会定位到空操作(_empty)方法来执行,利用这个机制,我们可以实现错误页面和一些URL的优化。
空模块的概念是指当系统找不到指定的模块名称的时候,系统会尝试定位空模块(EmptyAction),利用这个机制我们可以用来定制错误页面和进行URL的优化。
4. 模块分组
模块分组相关的配置参数包括:
配置参数 | 说明 |
---|---|
APP_GROUP_LIST | 项目分组列表(配置即表示开启分组) |
DEFAULT_GROUP | 默认分组(默认值为Home) |
TMPL_FILE_DEPR | 分组模板下面模块和操作的分隔符,默认值为“/” |
VAR_GROUP | 分组的URL参数名,默认为g(普通模式URL才需要) |
例如我们把当前的项目分成Home和Admin两个组,分别表示前台和后台功能,那么只需要在项目配置中添加下面的配置:
- ‘APP_GROUP_LIST‘ => ‘Home,Admin‘, //项目分组设定
- ‘DEFAULT_GROUP‘ => ‘Home‘, //默认分组
多个分组之间用逗号分隔即可,默认分组只允许设置一个。
5. URL伪静态
ThinkPHP支持伪静态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_HTML_SUFFIX‘=>‘.shtml‘
伪静态设置后,如果需要动态生成一致的URL,可以使用U方法在模板文件里面生成URL。
3.1版本开始,默认情况下,可以支持所有的静态后缀,并且会记录当前的伪静态后缀到常量__EXT__,但不会影响正常的页面访问。如果要获取当前的伪静态后缀,通过常量__EXT__获取即可。
如果只是希望支持配置的伪静态后缀,可以直接设置成可以支持多个后缀,例如:
- ‘URL_HTML_SUFFIX‘=>‘html|shmtl|xml‘ // 多个用 | 分割
如果设置了多个伪静态后缀的话,使用U函数生成的URL地址中会默认使用第一个后缀,也支持指定后缀生成url地址。
6. URL路由
ThinkPHP支持URL路由功能,要启用路由功能,需要设置URL_ROUTER_ON 参数为true。开启路由功能后,并且配置URL_ROUTE_RULES参数后,系统会自动进行路由检测,如果在路由定义里面找到和当前URL匹配的路由名称,就会进行路由解析和重定向。
详情见:http://doc.thinkphp.cn/manual/url_route.html
7. URL重写
详情见:http://doc.thinkphp.cn/manual/url_rewrite.html
8. URL生成
为了配合所使用的URL模式,我们需要能够动态的根据当前的URL设置生成对应的URL地址,为此,ThinkPHP内置提供了U方法,用于URL的动态生成,可以确保项目在移植过程中不受环境的影响。
U方法的定义规则如下(方括号内参数根据实际应用决定):
- U(‘[分组/模块/操作]?参数‘ [,‘参数‘,‘伪静态后缀‘,‘是否跳转‘,‘显示域名‘])
如果不定义项目和模块的话 就表示当前项目和模块名称,下面是一些简单的例子:
- U(‘User/add‘) // 生成User模块的add操作的URL地址
- U(‘Blog/read?id=1‘) // 生成Blog模块的read操作 并且id为1的URL地址
- U(‘Admin/User/select‘) // 生成Admin分组的User模块的select操作的URL地址
U方法的第二个参数支持数组和字符串两种定义方式,如果只是字符串方式的参数可以在第一个参数中定义,例如:
- U(‘Blog/cate‘,array(‘cate_id‘=>1,‘status‘=>1))
- U(‘Blog/cate‘,‘cate_id=1&status=1‘)
- U(‘Blog/cate?cate_id=1&status=1‘)
三种方式是等效的,都是 生成Blog模块的cate操作 并且cate_id为1 status为1的URL地址
但是不允许使用下面的定义方式来传参数
- U(‘Blog/cate/cate_id/1/status/1‘)
根据项目的不同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方法还可以支持路由,如果我们定义了一个路由规则为:
- ‘news/:id\d‘=>‘News/read‘
那么可以使用
- U(‘/news/1‘)
最终生成的URL地址是:
- http://serverName/index.php/news/1
注意:如果你是在模板文件中直接使用U方法的话,需要采用 {:U(‘参数1‘, ‘参数2‘…)} 的方式,具体参考模板引擎章节的8.3 使用函数内容。
如果你的应用涉及到多个子域名的操作地址,那么也可以在U方法里面指定需要生成地址的域名,例如:
- U(‘Blog/[email protected]‘,‘id=1‘);
@后面传入需要指定的域名即可。
此外,U方法的第5个参数如果设置为true,表示自动识别当前的域名,并且会自动根据子域名部署设置APP_SUB_DOMAIN_DEPLOY和APP_SUB_DOMAIN_RULES自动匹配生成当前地址的子域名。
如果开启了URL_CASE_INSENSITIVE,则会统一生成小写的URL地址。
9. URL大小写
只要在项目配置中,增加:
- ‘URL_CASE_INSENSITIVE‘ =>true
就可以实现URL访问不再区分大小写了。
这里需要注意一个地方,如果我们定义了一个UserTypeAction的模块类,那么URL的访问应该是:
- http://serverName/index.php/user_type/list
- //而不是
- http://serverName/index.php/usertype/list
利用系统提供的U方法可以为你自动生成相关的URL地址。
如果设置
- ‘URL_CASE_INSENSITIVE‘ =>false
的话,URL就又变成:
- http://serverName/index.php/UserType/list
注意:URL不区分大小写并不会改变系统的命名规范,并且只有按照系统的命名规范后才能正确的实现URL不区分大小写。
10. 前置和后置操作
系统会检测当前操作是否具有前置和后置操作,如果存在就会按照顺序执行,前置和后置操作的方法名是在要执行的方法前面加 _before_和_after_,例如:
- class CityAction extends Action{
- //前置操作方法
- public function _before_index(){
- echo ‘before<br/>‘;
- }
- public function index(){
- echo ‘index<br/>‘;
- }
- //后置操作方法
- public function _after_index(){
- echo ‘after<br/>‘;
- }
- }
对于任何操作方法我们都可以按照这样的规则来定义前置和后置方法。
如果当前的操作并没有定义操作方法,而是直接渲染模板文件,那么如果定义了前置 和后置方法的话,依然会生效。真正有模板输出的可能仅仅是当前的操作,前置和后置操作一般情况是没有任何输出的。
需要注意的是,在有些方法里面使用了exit或者错误输出之类的话 有可能不会再执行后置方法了。
例如,如果在当前操作里面调用了系统Action的error方法,那么将不会再执行后置操作,但是不影响success方法的后置方法执行。
11. 跨模块调用
例如,我们在Index模块调用User模块的操作方法
- class IndexAction extends Action{
- public function index(){
- //实例化UserAction
- $User = new UserAction();
- //其他用户操作
- //...
- $this->display(); //输出页面模板
- }
- }
因为系统会自动加载Action控制器,因此 我们不需要导入UserAction类就可以直接实例化。
并且为了方便跨模块调用,系统内置了A方法和R方法。 $User = A(‘User‘);
事实上,A方法还支持跨分组或者跨项目调用,默认情况下是调用当前项目下面的模块。
跨项目调用的格式是:
A(‘[项目名://][分组名/]模块名‘)
例如:
- A(‘User‘) //表示调用当前项目的User模块
- A(‘Admin://User‘) //表示调用Admin项目的User模块
- A(‘Admin/User‘) //表示调用Admin分组的User模块
- A(‘Admin://Tool/User‘) //表示调用Admin项目Tool分组的User模块
R方法表示调用一个模块的某个操作方法,调用格式是:
R(‘[项目名://][分组名/]模块名/操作名‘,array(‘参数1‘,‘参数2‘…))
例如:
- R(‘User/info‘) //表示调用当前项目的User模块的info操作方法
- R(‘Admin/User/info‘) //表示调用Admin分组的User模块的info操作方法
- R(‘Admin://Tool/User/info‘) //表示调用Admin项目Tool分组的User模块的info操作方法
R方法还支持对调用的操作方法需要传入参数,例如User模块中我们定义了一个info方法:
- class UserAction extends Action{
- protected function info($id){
- $User = M(‘User‘);
- $User->find($id);
- //...
- }
- }
接下来,我们可以在其他模块中调用:
- R(‘User/info‘,array(15))
表示调用当前项目的User模块的info操作方法,并且id参数传入15
12. 页面跳转
系统的Action类内置了两个跳转方法success和error,用于页面跳转提示,而且可以支持ajax提交。使用方法很简单,举例如下:
- $User = M(‘User‘); //实例化User对象
- $result = $User->add($data);
- if($result){
- //设置成功后跳转页面的地址,默认的返回页面是$_SERVER[‘HTTP_REFERER‘]
- $this->success(‘新增成功‘, ‘User/list‘);
- } else {
- //错误页面的默认跳转页面是返回前一页,通常不需要设置
- $this->error(‘新增失败‘);
- }
Success和error方法都有对应的模板,并且是可以设置的,默认的设置是两个方法对应的模板都是:
- //默认错误跳转对应的模板文件
- ‘TMPL_ACTION_ERROR‘ => THINK_PATH . ‘Tpl/dispatch_jump.tpl‘;
- //默认成功跳转对应的模板文件
- ‘TMPL_ACTION_SUCCESS‘ => THINK_PATH . ‘Tpl/dispatch_jump.tpl‘;
也可以使用项目内部的模板文件
- //默认错误跳转对应的模板文件
- ‘TMPL_ACTION_ERROR‘ => ‘Public:error‘;
- //默认成功跳转对应的模板文件
- ‘TMPL_ACTION_SUCCESS‘ => ‘Public:success‘;
模板文件可以使用模板标签,并且可以使用下面的模板变量:
$msgTitle | 操作标题 |
$message | 页面提示信息 |
$status | 操作状态 1表示成功 0 表示失败 具体还可以由项目本身定义规则 |
$waitSecond | 跳转等待时间 单位为秒 |
$jumpUrl | 跳转页面地址 |
success和error方法会自动判断当前请求是否属于Ajax请求,如果属于Ajax请求则会调用ajaxReturn方法返回信息,具体可以参考后面的AJAX返回部分。
3.1版本开始,error和success方法支持传值,无论是跳转模板方式还是ajax方式 都可以使用assign方式传参。例如:
- $this->assign(‘var1‘,‘value1‘);
- $this->assign(‘var2‘,‘value2‘);
- $this->error(‘错误的参数‘,‘要跳转的URL地址‘);
当正常方式提交的时候,var1和var2变量会赋值到错误模板的模板变量。
当采用AJAX方式提交的时候,会自动调用ajaxReturn方法传值过去(包括跳转的URL地址url和状态值status)
13. 重定向
Action类的redirect方法可以实现页面的重定向功能。
redirect方法的参数用法和U函数的用法一致(参考上面的URL生成部分),例如:
- //重定向到New模块的Category操作
- $this->redirect(‘New/category‘, array(‘cate_id‘ => 2), 5, ‘页面跳转中...‘);
上面的用法是停留5秒后跳转到News模块的category操作,并且显示页面跳转中字样,重定向后会改变当前的URL地址。
如果你仅仅是想重定向要一个指定的URL地址,而不是到某个模块的操作方法,可以直接使用redirect方法重定向,例如:
- //重定向到指定的URL地址
- redirect(‘/New/category/cate_id/2‘, 5, ‘页面跳转中...‘)
Redirect方法的第一个参数是一个URL地址。
14. 获取系统变量
- $this->方法名("变量名",["过滤方法"],["默认值"])
方法名可以支持:
方法名 | 含义 |
---|---|
_get | 获取GET参数 |
_post | 获取POST参数 |
_param | 自动判断请求类型获取GET、POST或者PUT参数(3.1新增) |
_request | 获取REQUEST 参数 |
_put | 获取PUT 参数 |
_session | 获取 $_SESSION 参数 |
_cookie | 获取 $_COOKIE 参数 |
_server | 获取 $_SERVER 参数 |
_globals | 获取 $GLOBALS参数 |
变量名:(必须)是要获取的系统变量的名称
过滤方法:(可选)可以用任何的内置函数或者自定义函数名,如果没有指定的话,采用默认的htmlspecialchars函数进行安全过滤(由DEFAULT_FILTER 参数配置),参数就是前面方法名获取到的值,也就是说如果调用:
- $this->_get("name");
最终调用的结果就是 htmlspecialchars($_GET["name"]),如果要改变过滤方法,可以使用:
- $this->_get("name","strip_tags");
默认值:(可选)是要获取的参数变量不存在的情况下设置的默认值,例如:
- $this->_get("id","strip_tags",0);
如果$_GET["id"] 不存在的话,会返回0。
如果没有设置任何默认值的话,系统默认返回NULL。
也可以支持多函数过滤。
例如,可以设置:
- ‘DEFAULT_FILTER‘=>‘htmlspecialchars,strip_tags‘
那么在控制器类如果调用
- $this->_get(‘id‘);
的话,会依次对$_GET[‘id‘] 变量进行htmlspecialchars和strip_tags方法过滤后返回结果。
下面调用方式也同样支持:
- $this->_get(‘id‘,‘htmlspecialchars,strip_tags‘,0);
其他变量获取方法用法相同。
支持获取全部变量,例如:
- $this->_get();
表示获取$_GET变量值。
支持不过滤处理
如果不希望过滤某个参数,可以使用
- $this->_get(‘id‘,false);
- $this->_post(‘id‘,false);
- //或者
- $this->_get(‘id‘,‘‘);
- $this->_post(‘id‘,‘‘);
第二个参数使用false或者空字符串则表示不作任何过滤处理,即使我们有配置默认的过滤方法。
如果我们忽略第二个参数调用的话
- $this->_get(‘id‘);
- $this->_post(‘id‘);
则表示调用默认的过滤方法(由DEFAULT_FILTER参数进行配置)。
3.1版本开始,Action类增加_param方法,可以自动根据当前请求类型(例如GET POST)获取参数。
例如:
- $this->_param(‘id‘);
当前为get方式提交的时候,就是获取$_GET[‘id‘](进行默认过滤后)的值
当前为post方式提交的时候,就是获取$_POST[‘id‘](进行默认过滤后)的值
还可以用_param方法获取URL中的参数
- $this->_param(0); // 获取PATHINFO地址中的第一个参数
- $this->_param(2); // 获取PATHINFO地址中的第3个参数
15. 判断请求类型
系统Action类内置了一些判断方法用于判断请求类型,包括:
方法 | 说明 |
---|---|
isGet | 判断是否是GET方式提交 |
isPost | 判断是否是POST方式提交 |
isPut | 判断是否是PUT方式提交 |
isDelete | 判断是否是DELETE方式提交 |
isHead | 判断是否是HEAD提交 |
使用举例如下:
- class UserAction extends Action{
- public function update(){
- if ($this->isPost()){
- $User = M(‘User‘);
- $User->create();
- $User->save();
- $this->success(‘保存完成‘);
- }else{
- $this->error(‘非法请求‘);
- }
- }
- }
另外还提供了一个判断当前是否属于AJAX提交的方法
isAjax 是否属于AJAX提交
需要注意的是,如果使用的是ThinkAjax或者自己写的Ajax类库的话,需要在表单里面添加一个隐藏域,告诉后台属于ajax方式提交,默认的隐藏域名称是ajax(可以通过VAR_AJAX_SUBMIT配置),如果是JQUERY类库的话,则无需添加任何隐藏域即可自动判断。
16. 获取URL参数
我们可以把URL地址 News/archive/2012/01/15 按照“/”分成多个参数,$_GET["_URL_"][0] 获取的就是News,$_GET["_URL_"][1]获取的就是archive,依次类推,可以通过数字索引获取所有的URL参数。
3.0版开始支持URL地址中的PATH_INFO方式的URL的参数获取方式,需要配置
VAR_URL_PARAMS参数,默认值是:
- ‘VAR_URL_PARAMS‘ => ‘_URL_‘, // PATHINFO URL参数变量
如果这个值不为空的话,就可以获取URL地址里面的PATH_INFO URL参数,例如
我们访问
- http://serverName.com/index.php/Blog/read/2012/03
则可以在Blog控制器的read操作方法里面采用
$GET[‘_URL_‘][2] 获取参数,表示获取PATH_INFO的URL参数
Blog/read/2012/03中的第3个参数(数组索引从0开始)
- $year = $GET[‘_URL_‘][2]; // 2012
- $month = $GET[‘_URL_‘][3]; // 03
3.1版本开始,建议使用_param方法获取URL参数,_param方法方法是3.1新增的方法,可以自动根据当前请求类型获取参数。
_param方法的用法同_get和_post等方法,区别在于,_param方法能够自动根据当前请求类型自动获取相应的参数,例如:
如果当前是get请求方式,
- $this->_param(‘id‘);
将会返回$_GET[‘id‘] 的处理数据
当采用POST请求方式的时候,同样的代码将会返回$_POST[‘id‘]的处理数据
如果采用的是PUT请求,那么会自动返回PUT的处理数据,而无需开发人员进行判断。
并且需要注意的是,无论是什么方式的请求,系统都可以支持URL参数的获取,如果C(‘VAR_URL_PARAMS‘)设置不为空的话,就可以使用:
- $this->_param(1);
- $this->_param(2);
来获取URL地址中的某个参数。
- $year = $this->_param(2);
- $month = $this->_param(3);
的方式来获取。
这样的好处是可以不需要使用路由功能就可以获取某个不规则的URL地址中的参数。
17. AJAX返回
系统支持任何的AJAX类库,Action类提供了ajaxReturn方法用于AJAX调用后返回数据给客户端。并且支持JSON、XML和EVAL三种方式给客户端接受数据,通过配置DEFAULT_AJAX_RETURN进行设置,默认配置采用JSON格式返回数据,在选择不同的AJAX类库的时候可以使用不同的方式返回数据。要使用ThinkPHP的ajaxReturn方法返回数据的话,需要遵守一定的返回数据的格式规范。
ThinkPHP返回的数据格式包括:
status | 操作状态 |
info | 提示信息 |
data | 返回数据 |
调用示例:
- $this->ajaxReturn(返回数据,提示信息,操作状态);
返回数据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方法。
在客户端接受数据的时候,根据使用的编码格式进行解析即可。
如果需要改变Ajax返回的数据格式,可以在控制器Action中增加ajaxAssign方法定义,定义格式如下:
- public function ajaxAssign(&$result) {
- // 返回数据中增加url属性
- $result[‘url‘] = $this->url;
- }
3.1版本以后,ajaxReturn方法可以更加灵活的进行ajax传值,并且废弃了ajaxAssign方法扩展。能够完全定义传值的数组和类型,例如:
- $data[‘status‘] = 1;
- $data[‘info‘] = ‘info‘;
- $data[‘size‘] = 9;
- $data[‘url‘] = $url;
- $this->ajaxReturn($data,‘JSON‘);
data传值数组可以随意定义。
改进后的ajaxReturn方法也兼容之前的写法:
- $this->ajaxReturn($data,‘info‘,1);
系统会自动把info和1两个参数并入$data数组中,等同于赋值
- $data[‘info‘] = ‘info‘;
- $data[‘status‘] = 1;
18. Action参数绑定
http://doc.thinkphp.cn/manual/action_param_bind.html
19. 多层控制器支持
3.1版本开始,控制器支持自定义分层。同时A方法增加第二个参数layer,用于设置控制器分层。
例如
- A(‘User‘,‘Event‘);
表示实例化Lib/Event/UserEvent.class.php。
UserEvent如果继承Action类的话,可以使用Action类所有的功能。
- A(‘User‘,‘Api‘);
表示实例化Lib/Api/UserApi.class.php
分层控制器仅用于内部调用,URL访问的控制器还是Action层,但是可以配置
DEFAULT_C_LAYER修改默认控制器层名称(该参数默认值为Action)。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
模型:
模型类的命名规则是除去表前缀的数据表名称,采用驼峰法命名,并且首字母大写,然后加上模型类的后缀定义Model,例如:
模型名(类名) | 约定对应数据表(假设数据库的前缀定义是 think_) |
---|---|
UserModel | think_user |
UserTypeModel | think_user_type |
如果你的规则和上面的系统约定不符合,那么需要设置Model类的数据表名称属性。
在ThinkPHP的模型里面,有几个关于数据表名称的属性定义:
属性 | 说明 |
---|---|
tableName | 不包含表前缀的数据表名称,一般情况下默认和模型名称相同,只有当你的表名和当前的模型类的名称不同的时候才需要定义。 |
trueTableName | 包含前缀的数据表名称,也就是数据库中的实际表名,该名称无需设置,只有当上面的规则都不适用的情况或者特殊情况下才需要设置。 |
dbName | 定义模型当前对应的数据库名称,只有当你当前的模型类对应的数据库名称和配置文件不同的时候才需要定义。 |
1. 模型实例化
在ThinkPHP中,可以无需进行任何模型定义。只有在需要封装单独的业务逻辑的时候,模型类才是必须被定义的。
1、实例化基础模型(Model) 类
在没有定义任何模型的时候,我们可以使用下面的方法实例化一个模型类来进行操作:
- //实例化User模型
- $User = new Model(‘User‘);
- //或者使用M()快捷方法实例化,和上面的方法是等效的
- $User = M(‘User‘);
- //执行其他的数据操作
- $User->select();
这种方法最简单高效,因为不需要定义任何的模型类,所以支持跨项目调用。缺点也是因为没有自定义的模型类,因此无法写入相关的业务逻辑,只能完成基本的CURD操作。
2、实例化其他公共模型类
- $User = new CommonModel(‘User‘);
模型类的实例化方法有三个参数,第一个参数是模型名称,第二个参数用于设置数据表的前缀(留空则取当前项目配置的表前缀),第三个参数用于设置当前使用的数据库连接信息(留空则取当前项目配置的数据库连接信息),例如:
- $User = new CommonModel(‘User‘,‘think_‘,‘db_config‘);
用M方法实现的话,上面的方法可以写成:
- $User = M(‘CommonModel:User‘,‘think_‘,‘db_config‘);
M方法默认是实例化Model类,第二个参数用于指定表前缀,第三个参数就可以指定其他的数据库连接信息。
模型类CommonModel必须继承Model。我们可以在CommonModel类里面定义一些通用的逻辑方法,就可以省去为每个数据表定义具体的模型类。
3、实例化用户自定义模型(×××Model)类
这种情况是使用的最多的,一个项目不可避免的需要定义自身的业务逻辑实现,就需要针对每个数据表定义一个模型类,例如UserModel 、InfoModel等等。
定义的模型类通常都是放到项目的Lib\Model目录下面。例如,
- <?php
- class UserModel extends Model{
- public function getTopUser(){
- //添加自己的业务逻辑
- // ...
- }
- }
其实模型类还可以继承一个用户自定义的公共模型类,而不是只能继承Model类。
要实例化自定义模型类,可以使用下面的方式:
- <?php
- //实例化自定义模型
- $User = new UserModel();
- //或者使用D快捷方法
- $User = D(‘User‘);
- //执行具体的数据操作
- $User->select();
D方法可以自动检测模型类,如果存在自定义的模型类,则实例化自定义模型类,如果不存在,则会实例化Model基类,同时对于已实例化过的模型,不会重复去实例化。
D方法还可以支持跨项目和分组调用,需要使用:
- //实例化Admin项目的User模型
- D(‘Admin://User‘)
- //实例化Admin分组的User模型
- D(‘Admin/User‘)
2. 字段定义
字段缓存保存在Runtime/Data/_fields/ 目录下面,缓存机制是每个模型对应一个字段缓存文件(而并非每个数据表对应一个字段缓存文件),命名格式是:
数据库名.模型名.php
字段缓存包括数据表的字段信息、主键字段和是否自动增长,如果开启字段类型验证的话还包括字段类型信息等等,无论是用M方法还是D方法,或者用原生的实例化模型类一般情况下只要是不开启调试模式都会生成字段缓存(字段缓存可以单独设置关闭)。从3.1版本开始,模型的字段缓存文件名全部转换成小写,避免重复生成。
可以通过设置DB_FIELDS_CACHE 参数来关闭字段自动缓存,如果在开发的时候经常变动数据库的结构,而不希望进行数据表的字段缓存,可以在项目配置文件中增加如下配置:
- ‘DB_FIELDS_CACHE‘=>false
注意:调试模式下面由于考虑到数据结构可能会经常变动,所以默认是关闭字段缓存的。
如果需要显式获取当前数据表的字段信息,可以使用模型类的getDbFields方法来获取当前数据对象的全部字段信息,例如:
- $fields = $User->getDbFields();
如果你在部署模式下面修改了数据表的字段信息,可能需要清空Data/_fields目录下面的缓存文件,让系统重新获取更新的数据表字段信息,否则会发生新增的字段无法写入数据库的问题。
如果不希望依赖字段缓存或者想提高性能,也可以在模型类里面手动定义数据表字段的名称,可以避免IO加载的效率开销,在模型类里面添加fields属性即可,定义格式如下:
- <?php
- class UserModel extends Model{
- protected $fields = array(
- ‘id‘, ‘username‘, ‘email‘, ‘age‘, ‘_pk‘ => ‘id‘, ‘_autoinc‘ => true
- );
- }
3. 数据主键
ThinkPHP的默认约定每个数据表的主键名采用统一的id作为标识,并且是自动增长类型的。系统会自动识别当前操作的数据表的字段信息和主键名称,所以即使你的主键不是id,也无需进行额外的设置,系统会自动识别。要在外部获取当前数据对象的主键名称,请使用下面的方法:
- $pk = $Model->getPk();
注意:目前不支持联合主键的自动获取和操作。
4. 属性访问
ThinkPHP的模型对象实例本身也是一个数据对象,可以支持对象和数组两种方式来访问数据属性,例如下面的方式采用数据对象的方式来访问User模型的属性:
- //实例化User模型
- $User = D(‘User‘);
- //查询用户数据
- $User->find(1);
- //获取name属性的值
- echo $User->name;
- //设置name属性的值
- $User->name = ‘ThinkPHP‘;
除了find方法会产生数据对象属性外,data方法和create方法也会产生数据对象,例如:
- $User = D(‘User‘);
- $User->create();
- echo $User->name;
还有一种属性的操作方式是通过返回数组的方式:
- //实例化User模型
- $User = D(‘User‘);
- //查询用户数据
- $data = $User->find(1);
- //获取name属性的值
- echo $data[‘name‘];
- //设置name属性的值
- $data[‘name‘] = ‘ThinkPHP‘;
两种方式的属性获取区别是一个是对象的属性,一个是数组的索引,开发人员可以根据自己的需要选择什么方式。
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_‘;
如果你没有定义模型类,而是使用的M方法操作的话,也可以支持跨库操作,例如:
- $User = M(‘user.User‘,‘other_‘);
表示实例化User模型,连接的是user数据库的other_user表。
6. 连接数据库
ThinkPHP内置了抽象数据库访问层,把不同的数据库操作封装起来,我们只需要使用公共的Db类进行操作,而无需针对不同的数据库写不同的代码和底层实现,Db类会自动调用相应的数据库驱动来处理。如果应用需要使用数据库,必须配置数据库连接信息,数据库的配置文件有多种定义方式。
常用的配置方式是在项目配置文件中添加下面的参数:
- <?php
- //项目配置文件
- return array(
- //数据库配置信息
- ‘DB_TYPE‘ => ‘mysql‘, // 数据库类型
- ‘DB_HOST‘ => ‘localhost‘, // 服务器地址
- ‘DB_NAME‘ => ‘thinkphp‘, // 数据库名
- ‘DB_USER‘ => ‘root‘, // 用户名
- ‘DB_PWD‘ => ‘‘, // 密码
- ‘DB_PORT‘ => 3306, // 端口
- ‘DB_PREFIX‘ => ‘think_‘, // 数据库表前缀
- //其他项目配置参数
- // ...
- );
或者采用如下配置
- ‘DB_DSN‘ => ‘mysql://username:[email protected]:3306/DbName‘
如果两种配置参数同时存在的话,DB_DSN配置参数优先。
注意:如果要设置分布式数据库,暂时不支持DB_DSN方式配置。
如果采用PDO驱动的话,则必须首先配置DB_TYPE 为pdo,然后还需要单独配置其他参数,例如:
- //PDO连接方式
- ‘DB_TYPE‘ => ‘pdo‘, // 数据库类型
- ‘DB_USER‘ => ‘root‘, // 用户名
- ‘DB_PWD‘ => ‘‘, // 密码
- ‘DB_PREFIX‘ => ‘think_‘, // 数据库表前缀
- ‘DB_DSN‘ => ‘mysql:host=localhost;dbname=thinkphp;charset=UTF-8‘
注意:PDO方式的DB_DSN配置格式有所区别,根据不同的数据库类型设置有所不同。
配置文件定义的数据库连接信息一般是系统默认采用的,因为一般一个项目的数据库访问配置是相同的。该方法系统在连接数据库的时候会自动获取,无需手动连接。可以对每个项目和不同的分组定义不同的数据库连接信息,如果开启了调试模式的话,还可以在不同的应用状态的配置文件里面定义独立的数据库配置信息。
第二种 在模型类里面定义connection属性
如果在某个模型类里面定义了connection属性的话,则实例化该自定义模型的时候会采用定义的数据库连接信息,而不是配置文件中设置的默认连接信息,通常用于某些数据表位于当前数据库连接之外的其它数据库,例如:
- //在模型里单独设置数据库连接信息
- protected $connection = array(
- ‘db_type‘ => ‘mysql‘,
- ‘db_user‘ => ‘root‘,
- ‘db_pwd‘ => ‘1234‘,
- ‘db_host‘ => ‘localhost‘,
- ‘db_port‘ => ‘3306‘,
- ‘db_name‘ => ‘thinkphp‘
- );
也可以采用DSN方式定义,例如:
- //或者使用DSN定义
- protected $connection = ‘mysql://root:[email protected]:3306/thinkphp‘;
如果我们已经在配置文件中配置了额外的数据库连接信息,例如:
- //数据库配置1
- ‘DB_CONFIG1‘ => array(
- ‘db_type‘ => ‘mysql‘,
- ‘db_user‘ => ‘root‘,
- ‘db_pwd‘ => ‘1234‘,
- ‘db_host‘ => ‘localhost‘,
- ‘db_port‘ => ‘3306‘,
- ‘db_name‘ => ‘thinkphp‘
- ),
- //数据库配置2
- ‘DB_CONFIG2‘ => ‘mysql://root:[email protected]:3306/thinkphp‘;
那么,我们可以把模型类的属性定义改为:
- //调用配置文件中的数据库配置1
- protected $connection = ‘DB_CONFIG1‘;
- //调用配置文件中的数据库配置2
- protected $connection = ‘DB_CONFIG2‘;
如果采用的是M方法实例化模型的话,也可以支持传入不同的数据库连接信息,例如:
- $User = M(‘User‘,‘other_‘,‘mysql://root:[email protected]/demo‘);
表示实例化User模型,连接的是demo数据库的other_user表,采用的连接信息是第三个参数配置的。如果我们在项目配置文件中已经配置了DB_CONFIG2的话,也可以采用:
- $User = M(‘User‘,‘other_‘,‘DB_CONFIG2‘);
如果你的个别数据表没有定义任何前缀的话,可以在前缀参数中传入NULL,例如:
- $User = M(‘User‘,Null,‘DB_CONFIG2‘);
表示实例化User模型,连接的是demo数据库的user表。需要注意的是,ThinkPHP的数据库连接的惰性的,所以并不是在实例化的时候就连接数据库,而是在有实际的数据操作的时候才会去连接数据库(额外的情况是,在系统第一次实例化模型的时候,会自动连接数据库获取相关模型类对应的数据表的字段信息)。
7. 切换数据库
只需要调用Model类的db方法,用法:
- Model->db("数据库编号","数据库配置");
数据库编号用数字格式,对于已经调用过的数据库连接,是不需要再传入数据库连接信息的,系统会自动记录。对于默认的数据库连接,内部的数据库编号是0,因此为了避免冲突,请不要再次定义数据库编号为0的数据库配置。
数据库配置的定义方式和模型定义connection属性一样,支持数组、字符串以及调用配置参数三种格式。
Db方法调用后返回当前的模型实例,直接可以继续进行模型的其他操作,所以该方法可以在查询的过程中动态切换,例如:
- $this->db(1,"mysql://root:[email protected]:3306/test")->query("查询SQL");
该方法添加了一个编号为1的数据库连接,并自动切换到当前的数据库连接。
当第二次切换到相同的数据库的时候,就不需要传入数据库连接信息了,可以直接使用:
- $this->db(1)->query("查询SQL");
如果需要切换到默认的数据库连接,只需要调用:
- $this->db(0);
如果我们已经在项目配置中定义了其他的数据库连接信息,例如:
- //数据库配置1
- ‘DB_CONFIG1‘ = array(
- ‘db_type‘ => ‘mysql‘,
- ‘db_user‘ => ‘root‘,
- ‘db_pwd‘ => ‘1234‘,
- ‘db_host‘ => ‘localhost‘,
- ‘db_port‘ => ‘3306‘,
- ‘db_name‘ => ‘thinkphp‘
- ),
- //数据库配置2
- ‘DB_CONFIG2‘ => ‘mysql://root:[email protected]:3306/thinkphp‘;
我们就可以直接在db方法中调用配置进行连接了:
- $this->db(1,"DB_CONFIG1")->query("查询SQL");
- $this->db(2,"DB_CONFIG2")->query("查询SQL");
如果切换数据库之后,数据表和当前不一致的话,可以使用table方法指定要操作的数据表:
- $this->db(1)->table("top_user")->find();
我们也可以直接用M方法切换数据库,例如:
- M("User","think_","mysql://root:[email protected]:3306/test")->query("查询SQL");
或者
- M("User","think_","DB_CONFIG1")->query("查询SQL");
8. 分布式数据库
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_RW_SEPARATE‘=>true,
在读写分离的情况下,默认第一个数据库配置是主服务器的配置信息,负责写入数据,如果设置了DB_MASTER_NUM参数,则可以支持多个主服务器写入。其它的都是从数据库的配置信息,负责读取数据,数量不限制。每次连接从服务器并且进行读取操作的时候,系统会随机进行在从服务器中选择。
CURD操作系统会自动判断当前执行的方法的读操作还是写操作,如果你用的是原生SQL,那么需要注意系统的默认规则:
写操作必须用模型的execute方法,读操作必须用模型的query方法,否则会发生主从读写错乱的情况。
注意:主从数据库的数据同步工作不在框架实现,需要数据库考虑自身的同步或者复制机制。
9. 创建数据
在进行数据操作之前,我们往往需要手动创建需要的数据,例如对于提交的表单数据:
- // 获取表单的POST数据
- $data[‘name‘] = $_POST[‘name‘];
- $data[‘email‘] = $_POST[‘email‘];
- // 更多的表单数据值获取
- //……
很简单的例子:
- // 实例化User模型
- $User = M(‘User‘);
- // 根据表单提交的POST数据创建数据对象
- $User->create();
- // 把创建的数据对象写入数据库
- $User->add();
Create方法支持从其它方式创建数据对象,例如,从其它的数据对象,或者数组等
- $data[‘name‘] = ‘ThinkPHP‘;
- $data[‘email‘] = ‘[email protected]‘;
- $User->create($data);
甚至还可以支持从对象创建新的数据对象
- // 从User数据对象创建新的Member数据对象
- $User = M("User");
- $User->find(1);
- $Member = M("Member");
- $Member->create($User);
Create方法创建的数据对象是保存在内存中,并没有实际写入到数据库中,直到使用add或者save方法才会真正写入数据库。
因此在没有调用add或者save方法之前,我们都可以改变create方法创建的数据对象,例如:
- $User = M(‘User‘);
- $User->create(); //创建User数据对象
- $User->status = 1; // 设置默认的用户状态
- $User->create_time = time(); // 设置用户的创建时间
- $User->add(); // 把用户对象写入数据库
如果只是想简单创建一个数据对象,并不需要完成一些额外的功能的话,可以使用data方法简单的创建数据对象。
使用如下:
- // 实例化User模型
- $User = M(‘User‘);
- // 创建数据后写入到数据库
- $data[‘name‘] = ‘ThinkPHP‘;
- $data[‘email‘] = ‘[email protected]‘;
- $User->data($data)->add();
Data方法也支持传入数组和对象,使用data方法创建的数据对象不会进行自动验证和过滤操作,请自行处理。但在进行add或者save操作的时候,数据表中不存在的字段以及非法的数据类型(例如对象、数组等非标量数据)是会自动过滤的,不用担心非数据表字段的写入导致SQL错误的问题。
安全提示:
create方法如果没有传值,默认取$_POST数据,如果用户提交的变量内容,含有可执行的html代码,请进行手工过滤。
- $_POST[‘title‘] = "<script>alert(1);</script>";
非法html代码可以使用htmlspecialchars进行编码,以防止用户提交的html代码在展示时被执行,以下是两种安全处理方法。
- $_POST[‘title‘] = htmlspecialchars($_POST[‘title‘]);
- M(‘User‘)->create();
- $data[‘title‘] = $this->_post(‘title‘, ‘htmlspecialchars‘);
- M(‘User‘)->create($data);
10. 字段映射
ThinkPHP的字段映射功能可以让你在表单中隐藏真正的数据表字段,而不用担心放弃自动创建表单对象的功能,假设我们的User表里面有username和email字段,我们需要映射成另外的字段,定义方式如下:
- Class UserModel extends Model{
- protected $_map = array(
- ‘name‘ =>‘username‘, // 把表单中name映射到数据表的username字段
- ‘mail‘ =>‘email‘, // 把表单中的mail映射到数据表的email字段
- );
- }
这样,在表单里面就可以直接使用name和mail名称作为表单数据提交了。在保存的时候会字段转换成定义的实际数据表字段。字段映射还可以支持对主键的映射。
如果我们需要把数据库中的数据显示在表单中,并且也支持字段映射的话,需要对查询的数据进行一下处理,处理方式是调用Model类的parseFieldsMap方法,例如:
- // 实例化User模型
- $User = M(‘User‘);
- $data = $User->find(3);
这个时候取出的data数据包含的是实际的username和email字段,为了方便便表单输出,我们需要处理成字段映射显示在表单中,就需要使用下面的代码处理:
- $data = $User->parseFieldsMap($data);
这样一来,data数据中就包含了name和mail字段数据了,而不再有username和email字段数据了。
11. 连贯操作
ThinkPHP模型基础类提供的连贯操作方法,可以有效的提高数据存取的代码清晰度和开发效率,并且支持所有的CURD操作。使用也比较简单, 假如我们现在要查询一个User表的满足状态为1的前10条记录,并希望按照用户的创建时间排序 ,代码如下:
- $User->where(‘status=1‘)->order(‘create_time‘)->limit(10)->select();
这里的where、order和limit方法就被称之为连贯操作方法,T除了select方法必须放到最后一个外(因为select方法并不是连贯操作方法),连贯操作T的方法调用顺序没有先后,例如,下面的代码和上面的等效:
- $User->order(‘create_time‘)->limit(10)->where(‘status=1‘)->select();
如果不习惯使用连贯操作的话,还支持直接使用参数进行查询的方式。例如上面的代码可以改写为:
- $User->select(array(‘order‘=>‘create_time‘,‘where‘=>‘status=1‘,‘limit‘=>‘10‘));
使用数组参数方式的话,索引的名称就是连贯操作的方法名称。其实T不仅仅是查询方法可以使用连贯操作,包括所有的CURD方法都可以使用,例如:
- $User->where(‘id=1‘)->field(‘id,name,email‘)->find();
- $User->where(‘status=1 and id=1‘)->delete();
连贯操作通常只有一个参数,并且仅在当此查询或者操作有效,完成后会自动清空连贯操作的所有传值(有个别特殊的连贯操作有多个参数,并且会记录当前的传值)。简而言之,连贯操作的结果不会带入以后的查询。
系统支持的连贯操作方法有:
连贯操作 | 作用 | 支持的参数类型 |
---|---|---|
where | 用于查询或者更新条件的定义 | 字符串、数组和对象 |
table | 用于定义要操作的数据表名称 | 字符串和数组 |
alias | 用于给当前数据表定义别名 | 字符串 |
data | 用于新增或者更新数据之前的数据对象赋值 | 数组和对象 |
field | 用于定义要查询的字段(支持字段排除) | 字符串和数组 |
order | 用于对结果排序 | 字符串和数组 |
limit | 用于限制查询结果数量 | 字符串和数字 |
page | 用于查询分页(内部会转换成limit) | 字符串和数字 |
group | 用于对查询的group支持 | 字符串 |
having | 用于对查询的having支持 | 字符串 |
join* | 用于对查询的join支持 | 字符串和数组 |
union* | 用于对查询的union支持 | 字符串、数组和对象 |
distinct | 用于查询的distinct支持 | 布尔值 |
lock | 用于数据库的锁机制 | 布尔值 |
cache | 用于查询缓存 | 支持多个参数 |
relation | 用于关联查询(需要关联模型支持) | 字符串 |
所有的连贯操作都返回当前的模型实例对象(this),其中带*标识的表示支持多次调用。
可参考:http://doc.thinkphp.cn/manual/continuous_operation.html
12. CURD操作
创建(Create)
在ThinkPHP中使用add方法新增数据到数据库(而并不是create方法)。
使用示例如下:
- $User = M("User"); // 实例化User对象
- $data[‘name‘] = ‘ThinkPHP‘;
- $data[‘email‘] = ‘[email protected]‘;
- $User->add($data);
或者使用data方法连贯操作
- $User->data($data)->add();
如果在add之前已经创建数据对象的话(例如使用了create或者data方法),add方法就不需要再传入数据了。
使用create方法的例子:
- $User = M("User"); // 实例化User对象
- // 根据表单提交的POST数据创建数据对象
- $User->create();
- $User->add(); // 根据条件保存修改的数据
如果你的主键是自动增长类型,并且如果插入数据成功的话,Add方法的返回值就是最新插入的主键值,可以直接获取。
读取(Read)
在ThinkPHP中读取数据的方式很多,通常分为读取数据和读取数据集。
读取数据集使用select方法(新版已经废除原来的findall方法):
使用示例:
- $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‘);
当只有一个字段的时候,默认返回一个值。
如果需要返回数组,可以用:
- $this->getField(‘id‘,true); // 获取id数组
如果传入多个字段的话,默认返回一个关联数组:
- $User = M("User"); // 实例化User对象
- // 获取所有用户的ID和昵称列表
- $list = $User->getField(‘id,nickname‘);
返回的list是一个数组,键名是用户的id, 键值是用户的昵称nickname。
如果传入多个字段的名称,例如:
- $list = $User->getField(‘id,nickname,email‘);
返回的是一个二维数组,类似select方法的返回结果,区别的是这个二维数组的键名是用户的id(准确的说是getField方法的第一个字段名)。
如果我们传入一个字符串分隔符:
- $list = $User->getField(‘id,nickname,email‘,‘:‘);
那么返回的结果就是一个数组,键名是用户id,键值是 nickname:email的输出字符串。
getField方法的sepa参数还可以支持限制数量,例如:
- $this->getField(‘id,name‘,5); // 限制返回5条记录
- $this->getField(‘id‘,3); // 获取id数组 限制3条记录
可以配合使用order方法使用。
更新(Update)
在ThinkPHP中使用save方法更新数据库,并且也支持连贯操作的使用。
- $User = M("User"); // 实例化User对象
- // 要修改的数据对象属性赋值
- $data[‘name‘] = ‘ThinkPHP‘;
- $data[‘email‘] = ‘[email protected]‘;
- $User->where(‘id=5‘)->save($data); // 根据条件保存修改的数据
为了保证数据库的安全,避免出错更新整个数据表,如果没有任何更新条件,数据对象本身也不包含主键字段的话,save方法不会更新任何数据库的记录。
因此下面的代码不会更改数据库的任何记录
- $User->save($data);
除非使用下面的方式:
- $User = M("User"); // 实例化User对象
- // 要修改的数据对象属性赋值
- $data[‘id‘] = 5;
- $data[‘name‘] = ‘ThinkPHP‘;
- $data[‘email‘] = ‘[email protected]‘;
- $User->save($data); // 根据条件保存修改的数据
如果id是数据表的主键的话,系统自动会把主键的值作为更新条件来更新其他字段的值。
还有一种方法是通过create或者data方法创建要更新的数据对象,然后进行保存操作,这样save方法的参数可以不需要传入。
- $User = M("User"); // 实例化User对象
- // 要修改的数据对象属性赋值
- $data[‘name‘] = ‘ThinkPHP‘;
- $data[‘email‘] = ‘[email protected]‘;
- $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的值
- $data = array(‘name‘=>‘ThinkPHP‘,‘email‘=>‘[email protected]‘);
- $User-> where(‘id=5‘)->setField($data);
而对于统计字段(通常指的是数字类型)的更新,系统还提供了setInc和setDec方法。
- $User = M("User"); // 实例化User对象
- $User->where(‘id=5‘)->setInc(‘score‘,3); // 用户的积分加3
- $User->where(‘id=5‘)->setInc(‘score‘); // 用户的积分加1
- $User->where(‘id=5‘)->setDec(‘score‘,5); // 用户的积分减5
- $User->where(‘id=5‘)->setDec(‘score‘); // 用户的积分减1
删除(Delete)
在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();
可参见:http://doc.thinkphp.cn/manual/curd.html
13. ActiveRecord
ThinkPHP实现了ActiveRecords模式的ORM模型,采用了非标准的ORM模型:表映射到类,记录映射到对象。
下面我们用AR模式来换一种方式重新完成CURD操作。
一、创建数据
- $User = M("User"); // 实例化User对象
- // 然后直接给数据对象赋值
- $User->name = ‘ThinkPHP‘;
- $User->email = ‘[email protected]‘;
- // 把数据对象添加到数据库
- $User->add();
如果使用了create方法创建数据对象的话,仍然可以在创建完成后进行赋值
- $User = D("User");
- $User->create(); // 创建User数据对象,默认通过表单提交的数据进行创建
- // 增加或者更改其中的属性
- $User->status = 1;
- $User->create_time = time();
- // 把数据对象添加到数据库
- $User->add();
二、查询记录
假如我们要查询主键为8的某个用户记录,如果按照之前的方式:
- $User = M("User"); // 实例化User对象
- // 查找id为8的用户数据
- $User->where(‘id=8‘)->find();
用AR模式的话可以直接写成:
- $User->find(8);
如果要根据某个字段查询,例如查询姓名为ThinkPHP的可以用:
- $User = M("User"); // 实例化User对象
- $User->getByName("ThinkPHP");
如果要查询数据集,可以直接使用:
- // 查找主键为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的多个数据
14. 自动验证
大多数情况下面,数据对象是由表单提交的$_POST数据创建。需要使用系统的自动验证功能,只需要在Model类里面定义$_validate属性,是由多个验证因子组成的二维数组。
验证因子格式:
- array(验证字段,验证规则,错误提示,[验证条件,附加规则,验证时间])
示例:
- 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{
- // 验证通过 可以进行其他数据操作
- }
多字段验证
自动验证功能中的function和callback规则可以支持多字段。
例子:
- protected $_validate = array(
- array(‘user_id,good_id‘, ‘checkIfOrderToday‘, ‘今天已经购买过,请明天再来‘, 1,‘callback‘, 1),
- );
- protected function checkIfOrderToday($data){
- $map = $data;
- $map[‘ctime‘] = array(array(‘gt‘,[开始时间]), array(‘lt‘, [结束时间]));
- if($this->where($map)->find())
- return false;
- else
- return true;
- }
批量验证
新版支持数据的批量验证功能,只需要在模型类里面设置patchValidate属性为true( 默认为false),设置批处理验证后,getError() 方法返回的错误信息是一个数组,返回格式是:
- array("字段名1"=>"错误提示1","字段名2"=>"错误提示2"... )
前端可以根据需要需要自行处理。
手动验证
3.1版本开始,可以使用validate方法实现动态和批量手动验证,例如:
- $this->validate($validate)->create();
其中$validate变量的规范和_validate属性的定义规则一致,而且还可以支持函数调用(由于PHP本身的限制,在类的属性定义中不能调用函数)。
通过这一改进,以前需要支持数据自动验证,必须定义模型类的情况已经不再出现,你完全可以通过M方法实例化模型类后使用动态设置完成自动验证操作。
另外还有一个check方法,用于对单个数据的手动验证,支持部分自动验证的规则,用法如下:
- check(‘验证数据‘,‘验证规则‘,‘验证类型‘)
验证类型支持 in between equal length regex expire ip_allow ip_deny,默认为regex
结果返回布尔值
- $model->check($value,‘email‘);
- $model->check($value,‘1,2,3‘,‘in‘);
可参见:http://doc.thinkphp.cn/manual/auto_validate.html
15. 命名范围
首先定义_scope属性:
- class NewsModel extends Model {
- protected $_scope = array(
- // 命名范围normal
- ‘normal‘=>array(
- ‘where‘=>array(‘status‘=>1),
- ),
- // 命名范围latest
- ‘latest‘=>array(
- ‘order‘=>‘create_time DESC‘,
- ‘limit‘=>10,
- ),
- );
- }
_scope属性是一个数组,每个数组项表示定义一个命名范围。
属性定义完成后,接下来就是使用scope方法进行命名范围的调用了,每调用一个命名范围,就相当于执行了命名范围中定义的相关操作选项。
调用某个命名范围
最简单的调用方式就直接调用某个命名范围,例如:
- $Model->scope(‘normal‘)->select();
- $Model->scope(‘latest‘)->select();
生成的SQL语句分别是:
- SELECT * FROM think_news WHERE status=1
- SELECT * FROM think_news ORDER BY create_time DESC LIMIT 10
调用多个命名范围
也可以支持同时调用多个命名范围定义,例如:
- $Model->scope(‘normal‘)->scope(‘latest‘)->select();
或者简化为:
- $Model->scope(‘normal,latest‘)->select();
生成的SQL都是:
- SELECT * FROM think_news WHERE status=1 ORDER BY create_time DESC LIMIT 10
如果两个命名范围的定义存在冲突,则后面调用的命名范围定义会覆盖前面的相同属性的定义。
默认命名范围
系统支持默认命名范围功能,如果你定义了一个default命名范围,例如:
- protected $_scope = array(
- // 默认的命名范围
- ‘default‘=>array(
- ‘where‘=>array(‘status‘=>1),
- ‘limit‘=>10,
- ),
- );
那么调用default命名范围可以直接使用:
- $Model->scope()->select();
命名范围调整
如果你需要在normal命名范围的基础上增加额外的调整,可以使用:
- $Model->scope(‘normal‘,array(‘limit‘=>5))->select();
生成的SQL语句是:
- SELECT * FROM think_news WHERE status=1 LIMIT 5
当然,也可以在两个命名范围的基础上进行调整,例如:
- $Model->scope(‘normal,latest‘,array(‘limit‘=>5))->select();
生成的SQL是:
- SELECT * FROM think_news WHERE status=1 ORDER BY create_time DESC LIMIT 5
自定义命名范围
又或者,干脆不用任何现有的命名范围,我直接传入一个命名范围:
- $Model->scope(array(‘field‘=>‘id,title‘,‘limit‘=>5,‘where‘=>‘status=1‘,‘order‘=>‘create_time DESC‘))->select();
这样,生成的SQL变成:
- SELECT id,title FROM think_news WHERE status=1 ORDER BY create_time DESC LIMIT 5
与连贯操作混合使用
命名范围一样可以和之前的连贯操作混合使用,例如定义了命名范围_scope属性:
- protected $_scope = array(
- ‘normal‘=>array(
- ‘where‘=>array(‘status‘=>1),
- ‘field‘=>‘id,title‘,
- ‘limit‘=>10,
- ),
- );
然后在使用的时候,可以这样调用:
- $Model->scope(‘normal‘)->limit(8)->order(‘id desc‘)->select();
这样,生成的SQL变成:
- SELECT id,title FROM think_news WHERE status=1 ORDER BY id desc LIMIT 8
如果定义的命名范围和连贯操作的属性有冲突,则后面调用的会覆盖前面的。
如果是这样调用:
- $Model->limit(8)->scope(‘normal‘)->order(‘id desc‘)->select();
生成的SQL则是:
- SELECT id,title FROM think_news WHERE status=1 ORDER BY id desc LIMIT 10
命名范围功能的优势在于可以一次定义多次调用,并且在项目中也能起到分工配合的规范,避免开发人员在写CURD操作的时候出现问题,项目经理只需要合理的规划命名范围即可。
16. 自动完成
在Model类定义 $_auto 属性,可以完成数据自动处理功能,用来处理默认值、数据过滤以及其他系统写入字段。$_auto属性是由多个填充因子组成的数组。
填充因子格式:
- array(填充字段,填充内容,[填充条件,附加规则])
示例:
- 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();
动态设置自动完成规则
还可以使用auto方法动态设置自动完成规则,例如:
- $this->auto($auto)->create();
其中$auto变量的规范和_auto属性的定义规则一致,而且还可以支持函数调用(由于PHP本身的限制,在类的属性定义中不能调用函数)。
通过这一改进,以前需要支持数据自动完成,必须定义模型类的情况已经不再出现,你完全可以通过M方法实例化模型类后使用动态设置完成自动完成操作。
17. 查询语言
查询方式
ThinkPHP可以支持直接使用字符串作为查询条件,但是大多数情况推荐使用索引数组或者对象来作为查询条件,因为会更加安全。
一、使用字符串作为查询条件
这是最传统的方式,但是安全性不高,例如:
- $User = M("User"); // 实例化User对象
- $User->where(‘type=1 AND status=1‘)->select();
最后生成的SQL语句是
SELECT * FROM think_user WHERE type=1 AND status=1
二、使用数组作为查询条件
- $User = M("User"); // 实例化User对象
- $condition[‘name‘] = ‘thinkphp‘;
- $condition[‘status‘] = 1;
- // 把查询条件传入查询方法
- $User->where($condition)->select();
最后生成的SQL语句是
SELECT * FROM think_user WHERE `name`=‘thinkphp‘ AND status=1
如果进行多字段查询,那么字段之间的默认逻辑关系是 逻辑与 AND,但是用下面的规则可以更改默认的逻辑判断,通过使用 _logic 定义查询逻辑:
- $User = M("User"); // 实例化User对象
- $condition[‘name‘] = ‘thinkphp‘;
- $condition[‘account‘] = ‘thinkphp‘;
- $condition[‘_logic‘] = ‘OR‘;
- // 把查询条件传入查询方法
- $User->where($condition)->select();
最后生成的SQL语句是
SELECT * FROM think_user WHERE `name`=‘thinkphp‘ OR `account`=‘thinkphp‘
三、使用对象方式来查询 (这里以stdClass内置对象为例)
- $User = M("User"); // 实例化User对象
- // 定义查询条件
- $condition = new stdClass();
- $condition->name = ‘thinkphp‘;
- $condition->status= 1;
- $User->where($condition)->select();
最后生成的SQL语句和上面一样
SELECT * FROM think_user WHERE `name`=‘thinkphp‘ AND status=1
使用对象方式查询和使用数组查询的效果是相同的,并且是可以互换的,大多数情况下,我们建议采用数组方式更加高效,后面我们会以数组方式为例来讲解具体的查询语言用法。
表达式查询
上面的查询条件仅仅是一个简单的相等判断,可以使用查询表达式支持更多的SQL查询语法,并且可以用于数组或者对象方式的查询(下面仅以数组方式为例说明),查询表达式的使用格式:
$map[‘字段名‘] = array(‘表达式‘,‘查询条件‘);
表达式不分大小写,支持的查询表达式有下面几种,分别表示的含义是:
表达式 | 含义 |
---|---|
EQ | 等于(=) |
NEQ | 不等于(<>) |
GT | 大于(>) |
EGT | 大于等于(>=) |
LT | 小于(<) |
ELT | 小于等于(<=) |
LIKE | 模糊查询 |
[NOT] BETWEEN | (不在)区间查询 |
[NOT] IN | (不在)IN 查询 |
EXP | 表达式查询,支持SQL语法 |
快捷查询
新版增加了快捷查询方式,可以进一步简化查询条件的写法,例如:
一、实现不同字段相同的查询条件
- $User = M("User"); // 实例化User对象
- $map[‘name|title‘] = ‘thinkphp‘;
- // 把查询条件传入查询方法
- $User->where($map)->select();
查询条件就变成 name= ‘thinkphp‘ OR title = ‘thinkphp‘
二、实现不同字段不同的查询条件
- $User = M("User"); // 实例化User对象
- $map[‘status&title‘] =array(‘1‘,‘thinkphp‘,‘_multi‘=>true);
- // 把查询条件传入查询方法
- $User->where($map)->select();
‘_multi‘=>true必须加在数组的最后,表示当前是多条件匹配,这样查询条件就变成 status= 1 AND title = ‘thinkphp‘ ,查询字段支持更多的,例如:
$map[‘status&score&title‘] =array(‘1‘,array(‘gt‘,‘0‘),‘thinkphp‘,‘_multi‘=>true);
查询条件就变成 status= 1 AND score >0 AND title = ‘thinkphp‘
注意:快捷查询方式中“|”和“&”不能同时使用。
区间查询
ThinkPHP支持对某个字段的区间查询,例如:
- $map[‘id‘] = array(array(‘gt‘,1),array(‘lt‘,10)) ;
得到的查询条件是: (`id` > 1) AND (`id` < 10)
- $map[‘id‘] = array(array(‘gt‘,3),array(‘lt‘,10), ‘or‘) ;
得到的查询条件是: (`id` > 3) OR (`id` < 10)
- $map[‘id‘] = array(array(‘neq‘,6),array(‘gt‘,3),‘and‘);
得到的查询条件是:(`id` != 6) AND (`id` > 3)
最后一个可以是AND、 OR或者 XOR运算符,如果不写,默认是AND运算。
组合查询
组合查询的主体还是采用数组方式查询,只是加入了一些特殊的查询支持,包括字符串模式查询(_string)、复合查询(_complex)、请求字符串查询(_query),混合查询中的特殊查询每次查询只能定义一个,由于采用数组的索引方式,索引相同的特殊查询会被覆盖。
一、字符串模式查询(采用_string 作为查询条件)
数组条件还可以和字符串条件混合使用,例如:
- $User = M("User"); // 实例化User对象
- $map[‘id‘] = array(‘neq‘,1);
- $map[‘name‘] = ‘ok‘;
- $map[‘_string‘] = ‘status=1 AND score>10‘;
- $User->where($map)->select();
最后得到的查询条件就成了:
( `id` != 1 ) AND ( `name` = ‘ok‘ ) AND ( status=1 AND score>10 )
二、请求字符串查询方式
请求字符串查询是一种类似于URL传参的方式,可以支持简单的条件相等判断。
- $map[‘id‘] = array(‘gt‘,‘100‘);
- $map[‘_query‘] = ‘status=1&score=100&_logic=or‘;
得到的查询条件是:`id`>100 AND (`status` = ‘1‘ OR `score` = ‘100‘)
三、复合查询
- $where[‘name‘] = array(‘like‘, ‘%thinkphp%‘);
- $where[‘title‘] = array(‘like‘,‘%thinkphp%‘);
- $where[‘_logic‘] = ‘or‘;
- $map[‘_complex‘] = $where;
- $map[‘id‘] = array(‘gt‘,1);
查询条件是
( id > 1) AND ( ( name like ‘%thinkphp%‘) OR ( title like ‘%thinkphp%‘) )
统计查询
方法 | 说明 |
---|---|
Count | 统计数量,参数是要统计的字段名(可选) |
Max | 获取最大值,参数是要统计的字段名(必须) |
Min | 获取最小值,参数是要统计的字段名(必须) |
Avg | 获取平均值,参数是要统计的字段名(必须) |
Sum | 获取总分,参数是要统计的字段名(必须) |
用法示例:
- $User = M("User"); // 实例化User对象
获取用户数:
- $userCount = $User->count();
或者根据字段统计:
- $userCount = $User->count("id");
获取用户的最大积分:
- $maxScore = $User->max(‘score‘);
获取积分大于0的用户的最小积分:
- $minScore = $User->where(‘score>0‘)->min(‘score‘);
获取用户的平均积分:
- $avgScore = $User->avg(‘score‘);
统计用户的总成绩:
- $sumScore = $User->sum(‘score‘);
并且所有的统计查询均支持连贯操作的使用。
定位查询
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();
SQL查询
1、query方法
query 执行SQL查询操作 | |
---|---|
用法 | query($sql,$parse=false) |
参数 | query(必须):要查询的SQL语句 parse(可选):是否需要解析SQL |
返回值 |
如果数据非法或者查询错误则返回false 否则返回查询结果数据集(同select方法) |
使用示例:
- $Model = new Model() // 实例化一个model对象 没有对应任何数据表
- $Model->query("select * from think_user where status=1");
如果你当前采用了分布式数据库,并且设置了读写分离的话,query方法始终是在读服务器执行,因此query方法对应的都是读操作,而不管你的SQL语句是什么。
2、execute方法
execute用于更新和写入数据的sql操作 | |
---|---|
用法 | execute($sql,$parse=false) |
参数 | query(必须):要执行的SQL语句 parse(可选):是否需要解析SQL |
返回值 | 如果数据非法或者查询错误则返回false 否则返回影响的记录数 |
使用示例:
- $Model = new Model() // 实例化一个model对象 没有对应任何数据表
- $Model->execute("update think_user set name=‘thinkPHP‘ where status=1");
如果你当前采用了分布式数据库,并且设置了读写分离的话,execute方法始终是在写服务器执行,因此execute方法对应的都是写操作,而不管你的SQL语句是什么。
3、其他技巧
自动获取当前表名
通常使用原生SQL需要手动加上当前要查询的表名,如果你的表名以后会变化的话,那么就需要修改每个原生SQL查询的sql语句了,针对这个情况,系统还提供了一个小的技巧来帮助解决这个问题。
例如:
- $model = M("User");
- $model->query(‘select * from __TABLE__ where status>1‘);
我们这里使用了__TABLE__ 这样一个字符串,系统在解析的时候会自动替换成当前模型对应的表名,这样就可以做到即使模型对应的表名有所变化,仍然不用修改原生的sql语句。
支持连贯操作和SQL解析
新版对query和execute两个原生SQL操作方法增加第二个参数支持, 表示是否需要解析SQL (默认为false 表示直接执行sql ),如果设为true 则会解析SQL中的特殊字符串 (需要配合连贯操作)。
例如,支持 如下写法:
- $model->table("think_user")
- ->where(array("name"=>"thinkphp"))
- ->field("id,name,email")
- ->query(‘select %FIELD% from %TABLE% %WHERE%‘,true);
其中query方法中的%FIELD%、%TABLE%和%WHERE%字符串会自动替换为同名的连贯操作方法的解析结果SQL,支持的替换字符串包括:
替换字符串 | 对应连贯操作方法 |
---|---|
%FIELD% | field |
%TABLE% | table |
%DISTINCT% | distinct |
%WHERE% | where |
%JOIN% | join |
%GROUP% | group |
%HAVING% | having |
%ORDER% | order |
%LIMIT% | limit |
%UNION% | union |
动态查询
借助PHP5语言的特性,ThinkPHP实现了动态查询,包括下面几种:
方法名 | 说明 | 举例 |
---|---|---|
getBy | 根据某个字段的值查询数据 | 例如,getByName,getByEmail |
getFieldBy | 根据某个字段查询并返回某个字段的值 | 例如,getFieldByName |
top | 获取前多少条记录(需要高级模型支持) | 例如,top8,top12 |
一、getBy动态查询
该查询方式针对数据表的字段进行查询。例如,User对象拥有id,name,email,address 等属性,那么我们就可以使用下面的查询方法来直接根据某个属性来查询符合条件的记录。
- $user = $User->getByName(‘liu21st‘);
- $user = $User->getByEmail(‘[email protected]‘);
- $user = $User->getByAddress(‘中国深圳‘);
暂时不支持多数据字段的动态查询方法,请使用find方法和select方法进行查询。
二、getFieldBy动态查询
针对某个字段查询并返回某个字段的值,例如
- $user = $User->getFieldByName(‘liu21st‘,‘id‘);
表示根据用户的name获取用户的id值。
三、top动态查询
ThinkPHP还提供了另外一种动态查询方式,就是获取符合条件的前N条记录(和定位查询一样,也要求当前模型类必须继承高级模型类后才能使用)。例如,我们需要获取当前用户中积分大于0,积分最高的前5位用户 :
- $User-> where(‘score>80‘)->order(‘score desc‘)->top5();
要获取积分的前8位可以改成:
- $User-> where(‘score>80‘)->order(‘score desc‘)->top8();
子查询
新版新增了子查询支持,有两种使用方式:
1、使用select方法
当select方法的参数为false的时候,表示不进行查询只是返回构建SQL,例如:
- // 首先构造子查询SQL
- $subQuery = $model->field(‘id,name‘)->table(‘tablename‘)->group(‘field‘)->where($where)->order(‘status‘)->select(false);
2、使用buildSql方法
- $subQuery = $model->field(‘id,name‘)->table(‘tablename‘)->group(‘field‘)->where($where)->order(‘status‘)->buildSql();
调用buildSql方法后不会进行实际的查询操作,而只是生成该次查询的SQL语句(为了避免混淆,会在SQL两边加上括号),然后我们直接在后续的查询中直接调用。
- // 利用子查询进行查询
- $model->table($subQuery.‘ a‘)->where()->order()->select()
构造的子查询SQL可用于TP的连贯操作方法,例如table where等。
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();
19. 字段排除
当使用下面的字段排除方式查询的时候
- $Model->field(‘create_time,read_count,comment_count‘,true);
第二个参数表示field方法采用的是排除机制,因此实际查询的字段是除create_time,read_count,comment_count之外的其他数据表所有字段,最终要查询的字段根据实际的数据表字段有所不同。
生成的SQL语句就变成了SELECT id,name,title,status FROM article
20. 事务支持
ThinkPHP提供了单数据库的事务支持,如果要在应用逻辑中使用事务。事务是针对数据库本身的,所以可以跨模型操作的 。
例如:
- // 在User模型中启动事务
- $User->startTrans();
- // 进行相关的业务逻辑操作
- $Info = M("Info"); // 实例化Info对象
- $Info->save($User); // 保存用户信息
- if (操作成功){
- // 提交事务
- $User->commit();
- }else{
- // 事务回滚
- $User->rollback();
- }
注意:系统提供的事务操作方法必须有数据库本身的支持,如果你的数据库或者数据表类型不支持事务,那么系统的事务操作是无效的。
21. 高级模型
http://doc.thinkphp.cn/manual/adv_model.html
22. 视图模型
http://doc.thinkphp.cn/manual/view_model.html
23. 关联模型
http://doc.thinkphp.cn/manual/relation_model.html
24. Mongo模型
http://doc.thinkphp.cn/manual/mongo_model.html
25. 动态模型
你可以从基本模型切换到高级模型或者视图模型,而当前的数据不会丢失,并可以控制要传递的参数和动态赋值。
要切换模型,可以使用:
- $User = M("User"); // 实例化User对象 是基础模型类的实例
- // 动态切换到高级模型类 执行top10查询操作
- $User->switchModel("Adv")->top10();
上面的写法也可以改成
- $User = M("AdvModel:User"); // 实例化User对象 是基础模型类的实例
- $User->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);
26. 虚拟模型
有些时候,我们建立模型类但又不需要进行数据库操作,仅仅是借助模型类来封装一些业务逻辑,那么可以借助虚拟模型来完成。虚拟模型不会自动连接数据库,因此也不会自动检测数据表和字段信息,有两种方式可以定义虚拟模型:
第一种:继承Model类
- Class UserModel extends Model {
- Protected $autoCheckFields = false;
- }
设置autoCheckFields属性为false后,就会关闭字段信息的自动检测,因为ThinkPHP采用的是惰性数据库连接,只要你不进行数据库查询操作,是不会连接数据库的。
1
第二种:不继承Model类
- Class UserModel { }
这种方式下面自定义模型类就是一个单纯的业务逻辑类,不能再使用模型的CURD操作方法,但是可以实例化其他的模型类进行相关操作,也可以在需要的时候直接实例化Db类进行数据库操作。
3.1版本开始,模型层(M)支持自定义分层。并且D方法,增加layer参数,具体分层的M类仍然继承Model类,用法示例:
实例化UserModel类(默认的情况)
文件位于项目的Lib/Model/UserModel.class.php
- D(‘User‘);
实例化UserLogic类 实现Logic分层
文件位于项目的Lib/Logic/UserLogic.class.php
- D(‘User‘,‘Logic‘);
实例化UserService类,实现Service分层
文件位于项目的Lib/Service/UserService.class.php
- D(‘User‘,‘Service‘);
可以配置DEFAULT_M_LAYER修改默认的模型层名称(该参数默认值为Model)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
视图:
ThinkPHP的视图有两个部分组成:View类和模板文件。Action控制器直接和View视图类打交道,把要输出的数据通过模板变量赋值的方式传递到视图类,而具体的输出工作则交由View视图类来进行,同时视图类还和模板引擎进行接口,包括完成布局渲染、输出替换、页面Trace等功能。
1. 模板定义
为了对模板文件更加有效的管理,ThinkPHP对模板文件进行目录划分,默认的模板文件定义规则是:
模板目录/[分组名/][模板主题/]模块名/操作名+模板后缀
模板目录默认是项目下面的Tpl, 当定义分组的情况下,会按照分组名分开子目录,新版模板主题默认是空(表示不启用模板主题功能),模板主题功能是为了多模板切换而设计的,如果有多个模板主题的话,可以用DEFAULT_THEME参数设置默认的模板主题名。
在每个模板主题下面,是以项目的模块名为目录,然后是每个模块的具体操作模板文件,例如:
User模块的add操作 对应的模板文件就应该是:
- Tpl/User/add.html
模板文件的默认后缀的情况是.html,也可以通过TMPL_TEMPLATE_SUFFIX来配置成其他的。
如果项目启用了模块分组功能(假设User模块属于Home分组),那么默认对应的模板文件可能变成 :
- Tpl/Home/User/add.html
当然,分组功能也提供了TMPL_FILE_DEPR参数来配置简化模板的目录层次。
例如 TMPL_FILE_DEPR如果配置成“_”的话,默认的模板文件就变成了:
- Tpl/Home/User_add.html
正是因为系统有这样一种模板文件自动识别的规则,所以通常的display方法无需带任何参数即可输出对应的模板。
2. 模板赋值
要在模板中输出变量,必须在在Action类中把变量传递给模板,视图类提供了assign方法对模板变量赋值,无论何种变量类型都统一使用assign赋值。
- $this->assign(‘name‘,$value);
- // 下面的写法是等效的
- $this->name = $value;
系统只会输出设定的变量,其它变量不会输出,一定程度上保证了变量的安全性。
如果要同时输出多个模板变量,可以使用下面的方式:
- $array[‘name‘] = ‘thinkphp‘;
- $array[‘email‘] = ‘[email protected]‘;
- $array[‘phone‘] = ‘12335678‘;
- $this->assign($array);
模板变量赋值后,怎么在模板文件中输出,需要根据选择的模板引擎来用不同的方法,如果使用的是内置的模板引擎,请参考后面的模板指南部分。如果你使用的是PHP本身作为模板引擎的话 ,就可以直接在模板文件里面输出了,如下:
- <?php
- echo $name.‘[‘.$email.‘‘.$phone.‘]‘;
如果要获得全部的模板变量,可以调用View类的get方法支持获取全部模板变量的值,例如:
- $this->get(‘name‘); // 获取name模板变量的值
- $this->get(); // 获取所有模板赋值变量的值
3. 模板输出
模板变量赋值后就需要调用模板文件来输出相关的变量,模板调用通过display方法来实现。我们在操作方法的最后使用:
- $this->display();
就可以输出模板。
一、调用当前模块的其他操作模板
格式:display(‘操作名‘)
例如,假设当前操作是User模块下面的read操作,我们需要调用User模块的edit操作模版,使用:
- $this->display(‘edit‘);
不需要写模板文件的路径和后缀。
二、调用其他模块的操作模板
格式:display(‘模块名:操作名‘)
例如,当前是User模块,我们需要调用Member模块的read操作模版 ,使用:
- $this->display(‘Member:read‘);
三、调用其他主题的操作模板
格式: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是一个实际存在的模板文件。如果使用的是相对路径的话,要注意当前位置是相对于项目的入口文件,而不是模板目录。
五、直接解析内容
Action类的display方法如果传入第四个参数,表示不读取模板文件而是直接解析内容。例如:
- $this->assign(‘foo‘,‘ThinkPHP‘);
- $this->show(‘Hello, {$foo}!‘);
会在页面输出: Hello,ThinkPHP!
直接输出的内容仍然支持模板布局功能。
show方法也可以支持指定编码和输出格式,例如:
- $this->show($content, ‘utf-8‘, ‘text/xml‘);
事实上,display方法还有其他的参数和用法。
有时候某个模板页面我们需要输出指定的编码,而不是默认的编码,可以使用:
- $this->display(‘Member:read‘, ‘gbk‘);
或者输出的模板文件不是text/html格式的,而是XML格式的,可以用:
- $this->display(‘Member:read‘, ‘utf-8‘, ‘text/xml‘);
如果你的网站输出编码不是默认的编码,可以使用:
- ‘DEFAULT_CHARSET‘=> ‘gbk‘
如果要输出XML格式的,可以用:
- ‘TMPL_CONTENT_TYPE‘=> ‘text/xml‘
4. 模板替换
在进行模板输出之前,系统还会对渲染的模板结果进行一些模板的特殊字符串替换操作,也就是实现了模板输出的替换和过滤。模板替换适用于所有的模板引擎,包括原生的PHP模板。这个机制可以使得模板文件的定义更加方便,默认的替换规则有:
../Public: 会被替换成当前项目的公共模板目录 通常是 /项目目录/Tpl/当前主题/Public/
__TMPL__: 会替换成项目的模板目录 通常是 /项目目录/Tpl/当前主题/
(注:为了部署安全考虑,../Public和__TMPL__不再建议使用)
__PUBLIC__:会被替换成当前网站的公共目录 通常是 /Public/
__ROOT__: 会替换成当前网站的地址(不含域名)
__APP__: 会替换成当前项目的URL地址 (不含域名)
__GROUP__:会替换成当前分组的URL地址 (不含域名)
__URL__: 会替换成当前模块的URL地址(不含域名)
__ACTION__:会替换成当前操作的URL地址 (不含域名)
__SELF__: 会替换成当前的页面URL
注意这些特殊的字符串是严格区别大小写的,并且这些特殊字符串的替换规则是可以更改或者增加的,我们只需要在项目配置文件中配置TMPL_PARSE_STRING就可以完成。如果有相同的数组索引,就会更改系统的默认规则。例如:
- ‘TMPL_PARSE_STRING‘ =>array(
- ‘__PUBLIC__‘ => ‘/Common‘, // 更改默认的/Public 替换规则
- ‘__JS__‘ => ‘/Public/JS/‘, // 增加新的JS类库路径替换规则
- ‘__UPLOAD__‘ => ‘/Uploads‘, // 增加新的上传路径替换规则
- )
有了模板替换规则后,页面上所有的__PUBLIC__ 字符串都会被替换,那如果确实需要输出__PUBLIC__ 字符串到模板呢,我们可以通过增加替换规则的方式,例如:
- ‘TMPL_PARSE_STRING‘ =>array(
- ‘--PUBLIC--‘ => ‘__PUBLIC__‘, // 采用新规则输出/Public字符串
- )
这样增加替换规则后,如果我们要输出__PUBLIC__ 字符串,只需要在模板中添加--PUBLIC--,其他替换字符串的输出方式类似。
5. 获取内容
有些时候我们不想直接输出模板内容,而是希望对内容再进行一些处理后输出,就可以使用fetch方法来获取解析后的模板内容,在Action类里面使用:
- $content = $this->fetch();
fetch的参数用法和Display方法基本一致,也可以使用:
- $content = $this->fetch(‘Member:read‘);
区别就在于display方法直接输出模板文件渲染后的内容,而fetch方法是返回模板文件渲染后的内容。如何对返回的结果content进行处理,完全由开发人员自行决定了。这是模板替换的另外一种高级方式,比较灵活,而且不需要通过配置的方式。
注意,fetch方法仍然会执行上面的模板替换操作。
6. 模板引擎
系统支持原生的PHP模板,而且本身内置了一个基于XML的高效的编译型模板引擎,系统默认使用的模板引擎是内置模板引擎,关于这个模板引擎的标签详细使用可以参考模板指南部分。
内置的模板引擎也可以直接支持在模板文件中采用PHP原生代码和模板标签的混合使用,如果需要完全使用PHP本身作为模板引擎,可以配置:
- ‘TMPL_ENGINE_TYPE‘ =>‘PHP‘
可以达到最佳的效率。
如果你使用了其他的模板引擎,只需要设置TMPL_ENGINE_TYPE参数为相关的模板引擎名称即可。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
模板引擎:
ThinkPHP内置了一个基于XML的性能卓越的模板引擎 ThinkTemplate,这是一个专门为ThinkPHP服务的内置模板引擎。ThinkTemplate是一个使用了XML标签库技术的编译型模板引擎,支持两种类型的模板标签,使用了动态编译和缓存技术,而且支持自定义标签库。
每个模板文件在执行过程中都会生成一个编译后的缓存文件,其实就是一个可以运行的PHP文件。模板缓存默认位于项目的Runtime/Cache目录下面,以模板文件的md5编码作为缓存文件名保存的。如果在模板标签的使用过程中发现问题,可以尝试通过查看模板缓存文件找到问题所在。
内置的模板引擎支持普通标签和XML标签方式两种标签定义,分别用于不同的目的:
普通标签 | 主要用于输出变量和做一些基本的操作 |
XML标签 | 主要完成一些逻辑判断、控制和循环输出,并且可扩展 |
1. 变量输出
如果我们在Action中赋值了一个name模板变量:
- $name = ‘ThinkPHP‘;
- $this->assign(‘name‘,$name);
使用内置的模板引擎输出变量,只需要在模版文件使用:
- {$name}
模板编译后的结果就是
- <?php echo($name);?>
注意模板标签的{和$之间不能有任何的空格,否则标签无效。
普通标签默认开始标记是 {,结束标记是 }。也可以通过设置TMPL_L_DELIM和TMPL_R_DELIM进行更改。例如,我们在项目配置文件中定义:
- ‘TMPL_L_DELIM‘=>‘<{‘,
- ‘TMPL_R_DELIM‘=>‘}>‘,
那么,上面的变量输出标签就应该改成:
- <{$name}>
如果TMPL_VAR_IDENTIFY设置为array,那么
{$user.name}和{$user[‘name‘]}等效,也就是输出数组变量。
如果TMPL_VAR_IDENTIFY设置为obj,那么
{$user.name}和{$user:name}等效,也就是输出对象的属性。
如果TMPL_VAR_IDENTIFY留空的话,系统会自动判断要输出的变量是数组还是对象,这种方式会一定程度上影响效率,而且只支持二维数组和两级对象属性。
如果TMPL_VAR_IDENTIFY留空的话,系统会自动判断要输出的变量是数组还是对象,这种方式会一定程度上影响效率,而且只支持二维数组和两级对象属性。
如果是多维数组或者多层对象属性的输出,可以使用下面的定义方式:
- {$user.sub.name}// 使用点语法输出
或者使用
- {$user[‘sub‘][‘name‘]}// 输出三维数组的值
- {$user:sub:name}// 输出对象的多级属性
2. 系统变量
除了常规变量的输出外,模板引擎还支持系统变量和系统常量、以及系统特殊变量的输出。它们的输出不需要事先赋值给某个模板变量。系统变量的输出必须以$Think.打头,并且仍然可以支持使用函数。常用的系统变量输出包括下面:
用法 | 含义 | 例子 |
---|---|---|
$Think.server | 获取$_SERVER | {$Think.server.php_self} |
$Think.get | 获取$_GET | {$Think.get.id} |
$Think.post | 获取$_POST | {$Think.post.name} |
$Think.request | 获取$_REQUEST | {$Think.request.user_id} |
$Think.cookie | 获取$_COOKIE | {$Think.cookie.username} |
$Think.session | 获取$_SESSION | {$Think.session.user_id} |
$Think.config | 获取系统配置参数 | {$Think.config.app_status} |
$Think.lang | 获取系统语言变量 | {$Think.lang.user_type} |
$Think.const | 获取系统常量 | {$Think.const.app_name}或{$Think.APP_NAME} |
$Think.env | 获取环境变量 | {$Think.env.HOSTNAME} |
$Think.version | 获取框架版本号 | {$Think.version} |
$Think.now | 获取当前时间 | {$Think.now} |
$Think.template | 获取当前模板 | {$Think.template} |
$Think.ldelim | 获取模板左界定符 | {$Think.ldelim} |
$Think.rdelim | 获取模板右界定符 | {$Think.rdelim} |
1、系统变量:包括server、session、post、get、request、cookie
2、系统常量:使用$Think.const 输出
3、特殊变量:由ThinkPHP系统内部定义的常量
4、配置参数:输出项目的配置参数值
- {$Think.config.db_charset}
输出的值和C(‘db_charset‘) 的返回结果是一样的。
也可以输出二维的配置参数,例如:
- {$Think.config.user.user_name}
5、语言变量:输出项目的当前语言定义值
- {$Think.lang.page_error}
输出的值和L(‘page_error‘)的返回结果是一样的。
3. 使用函数
用于模板标签的函数可以是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‘);?>
4. 默认值输出
如果输出的模板变量没有值,但是我们需要在显示的时候赋予一个默认值的话,可以使用default语法,格式:
{$变量|default="默认值"}
这里的default不是函数,而是系统的一个语法规则,例如:
- {$user.nickname|default="这家伙很懒,什么也没留下"}
对系统变量的输出也可以支持默认值,例如:
- {$Think.post.name|default="名称为空"}
默认值支持Html语法。
5. 使用运算符
内置模板引擎包含了运算符的支持,包括对“+”“ –” “*” “/”和“%”的支持。
在使用运算符的时候,不再支持点语法和常规的函数用法,例如:
- {$user.score+10} 是错误的
- {$user[‘score‘]+10} 是正确的
- {$user[‘score‘]*$user[‘level‘]} 正确的
- {$user[‘score‘]|myFun*10} 错误的
- {$user[‘score‘]+myFun($user[‘level‘])} 正确的
6. 内置标签
系统内置标签库的所有标签无需引入标签库即可直接使用。XML标签有两种,包括闭合标签和开放标签,一个标签在定义的时候就已经决定了是否是闭合标签还是开放标签,不可混合使用,例如:
闭合标签:
- <include file="read" />
开放标签:
- <gt name="name" value="5">value</gt>
内置支持的标签和属性列表如下:
标签名 | 作用 | 包含属性 |
---|---|---|
include | 包含外部模板文件(闭合) | file |
import | 导入资源文件(闭合 包括js css load别名) | file,href,type,value,basepath |
volist | 循环数组数据输出 | name,id,offset,length,key,mod |
foreach | 数组或对象遍历输出 | name,item,key |
for | For循环数据输出 | name,from,to,before,step |
switch | 分支判断输出 | name |
case | 分支判断输出(必须和switch配套使用) | value,break |
default | 默认情况输出(闭合 必须和switch配套使用) | 无 |
compare | 比较输出(包括eq neq lt gt egt elt heq nheq等别名) | name,value,type |
range | 范围判断输出(包括in notin between notbetween别名) | name,value,type |
present | 判断是否赋值 | name |
notpresent | 判断是否尚未赋值 | name |
empty | 判断数据是否为空 | name |
notempty | 判断数据是否不为空 | name |
defined | 判断常量是否定义 | name |
notdefined | 判断常量是否未定义 | name |
define | 常量定义(闭合) | name,value |
assign | 变量赋值(闭合) | name,value |
if | 条件判断输出 | condition |
elseif | 条件判断输出(闭合 必须和if标签配套使用) | condition |
else | 条件不成立输出(闭合 可用于其他标签) | 无 |
php | 使用php代码 | 无 |
7. 包含文件
可以使用Include标签来包含外部的模板文件,使用方法如下:
include标签(包含外部模板文件) | |
---|---|
闭合 | 闭合标签 |
属性 | file(必须):要包含的模板文件,支持变量 |
1、 使用完整文件名包含
格式:<include file="完整模板文件名" />
- <include file="./Tpl/default/Public/header.html" />
2、包含当前模块的其他操作模板文件
格式:<include file="操作名" />
- <include file="read" />
操作模板无需带后缀。
4、包含其他模板主题的模块操作模板
格式:<include file="主题名:模块名:操作名" />
- <include file="blue:User:read" />
5、 用变量控制要导入的模版
格式:<include file="$变量名" />
- <include file="$tplName" />
给$tplName赋不同的值就可以包含不同的模板文件,变量的值的用法和上面的用法相同。
无论你使用什么方式包含外部模板,Include标签支持在包含文件的同时传入参数,注意:由于模板解析的特点,从入口模板开始解析,如果外部模板有所更改,模板引擎并不会重新编译模板,除非在调试模式下或者缓存已经过期。如果部署模式下修改了包含的外部模板文件后,需要把模块的缓存目录清空,否则无法生效。
3.1版本开始,include标签支持导入多个模板,用逗号分割即可,例如:
- <include file=‘file1,file2‘ />
8. 导入文件
import标签(包含外部模板文件) | |
---|---|
闭合 | 闭合标签 |
属性 | file(必须):要包含的模板文件,支持变量 |
- <import type=‘js‘ file="Js.Util.Array" />
Type属性默认是js。还可以支持多个文件批量导入,例如:
- <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标签(采用url方式引入资源文件) | |
---|---|
闭合 | 闭合标签 |
属性 | href(必须):要引入的资源文件url地址,支持变量 |
- <load href="/Book/Tpl/Home/Public/Js/Common.js" />
- <load href="/Book/Tpl/Home/Public/Css/common.css" />
系统还提供了两个标签别名js和css 用法和load一致,例如:
- <js href="/Public/Js/Common.js" />
- <css href="/Book/Tpl/Home/Public/Css/common.css" />
9. Volist标签
Volist标签主要用于在模板中循环输出数据集或者多维数组。
volist标签(循环输出数据) | |
---|---|
闭合 | 非闭合标签 |
属性 |
name(必须):要输出的数据模板变量 id(必须):循环变量 offset(可选):要输出数据的offset length(可选):输出数据的长度 key(可选):循环的key变量,默认值为i mod(可选):对key值取模,默认为2 empty(可选):如果数据为空显示的字符串 |
通常模型的select方法返回的结果是一个二维数组,可以直接使用volist标签进行输出。
在Action中首先对模版赋值:
- $User = M(‘User‘);
- $list = $User->select();
- $this->assign(‘list‘,$list);
在模版定义如下,循环输出用户的编号和姓名:
- <volist name="list" id="vo">
- {$vo.id}
- {$vo.name}
- </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>
从2.1版开始允许在模板中直接使用函数设定数据集,而不需要在控制器中给模板变量赋值传入数据集变量,如:
- <volist name=":fun(‘arg‘)" id="vo">{$vo.name}</volist>
10. Foreach标签
foreach标签也是用于循环输出
foreach标签(循环输出数据) | |
---|---|
闭合 | 非闭合标签 |
属性 | name(必须):要输出的数据模板变量 item(必须):循环单元变量 key(可选):循环的key变量,默认值为key |
- <foreach name="list" item="vo">
- {$vo.id}
- {$vo.name}
- </foreach>
Foreach标签相对比volist标签简洁,没有volist标签那么多的功能。优势是可以对对象进行遍历输出,而volist标签通常是用于输出数组。
11. For标签
For标签用于实现for循环,格式为:
for标签(循环输出数据) | |
---|---|
闭合 | 非闭合标签 |
属性 | start(必须):循环变量开始值 end(必须):循环变量结束值 name(可选):循环变量名,默认值为i step(可选):步进值,默认值为1 comparison(可选):判断条件,默认为lt |
- <for start="开始值" end="结束值" comparison="" step="步进值" name="循环变量名" >
- </for>
- <for start="1" end="100">
- {$i}
- </for>
解析后的代码是
- for ($i=1;$i<100;$i+=1){
- echo $i;
- }
12. Switch标签
- <switch name="变量" >
- <case value="值1" break="0或1">输出内容1</case>
- <case value="值2">输出内容2</case>
- <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>
Case标签还有一个break属性,表示是否需要break,默认是会自动添加break,如果不要break,可以使用:
- <switch name="Think.get.userId|abs">
- <case value="1" break="0">admin</case>
- <case value="2">admin</case>
- <default />default
- </switch>
也可以对case的value属性使用变量,例如:
- <switch name="User.userId">
- <case value="$adminId">admin</case>
- <case value="$memberId">member</case>
- <default />default
- </switch>
使用变量方式的情况下,不再支持多个条件的同时判断。
13. 比较标签
- <比较标签 name="变量" value="值">内容</比较标签>
系统支持的比较标签以及所表示的含义分别是:
eq或者 equal | 等于 |
neq 或者notequal | 不等于 |
gt | 大于 |
egt | 大于等于 |
lt | 小于 |
elt | 小于等于 |
heq | 恒等于 |
nheq | 不恒等于 |
例如,要求name变量的值等于value就输出,可以使用:
- <eq name="name" value="value">value</eq>
当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|strlen" value="5">{$vo.name}</eq>
变量名可以支持系统变量的方式,例如:
- <eq name="Think.get.name" value="value">相等<else/>不相等</eq>
14. 三元运算
模板可以支持三元运算符,例如:
- {$status?‘正常‘:‘错误‘}
- {$info[‘status‘]?$info[‘msg‘]:$info[‘error‘]}
注意:三元运算符中暂时不支持点语法。
15. 范围判断标签
Range标签用于判断某个变量是否在某个范围之内:
范围判断标签(包括innotinbetween notbetween) | |
---|---|
闭合 | 非闭合标签 |
属性 | name(必须):变量名 value(必须):要比较的范围值,支持变量 |
可以使用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>
可以使用between标签来判断变量是否在某个区间范围内,可以使用:
- <between name="id"value="1,10">输出内容1</between>
可以使用notbetween标签来判断变量不在某个范围内:
- <notbetween name="id"value="1,10">输出内容1</notbetween>
当使用between标签的时候,value只需要一个区间范围,也就是只支持两个值,后面的值无效。
所有的范围判断标签的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。
16. Present标签和Empty标签
可以使用present标签来判断模板变量是否已经赋值,
present标签和notpresent标签 | |
---|---|
闭合 | 非闭合标签 |
属性 | name(必须):变量名 |
配合 | 可以结合else标签一起使用 |
- <present name="name">name已经赋值</present>
如果判断没有赋值,可以使用:
- <notpresent name="name">name还没有赋值</notpresent>
可以把上面两个标签合并成为:
- <present name="name">name已经赋值<else /> name还没有赋值</present>
可以使用empty标签判断模板变量是否为空,
empty标签和notempty标签 | |
---|---|
闭合 | 非闭合标签 |
属性 | name(必须):变量名 |
配合 | 可以结合else标签一起使用 |
- <empty name="name">name为空值</empty>
如果判断没有赋值,可以使用:
- <notempty name="name">name不为空</notempty>
可以把上面两个标签合并成为:
- <empty name="name">name为空<else /> name不为空</empty>
17. Defined标签和Define标签
可以使用defined标签判断常量是否已经有定义:
defined标签和notdefined标签 | |
---|---|
闭合 | 非闭合标签 |
属性 | name(必须):变量名 |
- <defined name="NAME">NAME常量已经定义</defined>
如果判断没有被定义,可以使用:
- <notdefined name="NAME">NAME常量未定义</notdefined>
可以把上面两个标签合并成为:
- <defined name="NAME">NAME常量已经定义<else /> NAME常量未定义</defined>
可以使用define标签进行常量定义:
defined标签和notdefined标签 | |
---|---|
闭合 | 闭合标签 |
属性 | name(必须):常量名 value(必须):常量值,支持变量 |
配合 | 可以结合else标签一起使用 |
- <define name="MY_DEFINE_NAME"value="3"/>
在运行模板的时候 定义了一个MY_DEFINE_NAME的常量。
18. Assign标签
可以使用assign标签进行赋值:
assign标签(在模板中给变量赋值) | |
---|---|
闭合 | 闭合标签 |
属性 | name(必须):模板变量名 value(必须):变量值,支持变量 |
- <assign name="var" value="123" />
在运行模板的时候 赋值了一个var的变量,值是123。
19. IF标签
用法示例:
- <if condition="($name eq 1) OR ($name gt 100) "> value1
- <elseif condition="$name eq 2"/>value2
- <else /> value3
- </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标签来直接书写代码。
20. 标签嵌套
模板引擎支持标签的多层嵌套功能,可以对标签库的标签指定可以嵌套。
系统内置的标签中,volist、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配置参数,例如:
- ‘TAG_NESTED_LEVEL‘ =>5
可以改变循环嵌套级别为5级。
21. 使用PHP代码
- <php>echo ‘Hello,world!‘;</php>
注意:php标签或者php代码里面就不能再使用标签(包括普通标签和XML标签)了,因此下面的几种方式都是无效的:
- <php><eq name=‘name‘value=‘value‘>value</eq></php>
简而言之,在PHP标签里面不能再使用PHP本身不支持的代码。
如果设置了TMPL_DENY_PHP参数为true,就不能在模板中使用原生的PHP代码,但是仍然支持PHP标签输出。
22. 模板布局
第一种方式是 以布局模板为入口的方式
该方式需要配置开启LAYOUT_ON 参数(默认不开启),并且设置布局入口文件名LAYOUT_NAME(默认为layout)。
开启LAYOUT_ON后,我们的模板渲染流程就有所变化,例如:
- Class UserAction extends Action {
- Public function add() {
- $this->display(‘add‘);
- }
- }
在不开启LAYOUT_ON布局模板之前,会直接渲染Tpl/User/add.html 模板文件,开启之后,首先会渲染Tpl/layout.html 模板,布局模板的写法和其他模板的写法类似,本身也可以支持所有的模板标签以及包含文件,区别在于有一个特定的输出替换变量{__CONTENT__},例如,下面是一个典型的layout.html模板的写法:
- {__CONTENT__}
读取layout模板之后,会再解析User/add.html 模板文件,并把解析后的内容替换到layout布局模板文件的{__CONTENT__} 特定字符串。
采用这种布局方式的情况下,一旦User/add.html 模板文件或者layout.html布局模板文件发生修改,都会导致模板重新编译。
如果项目需要使用不同的布局模板,可以动态的配置LAYOUT_NAME参数实现。
如果某些页面不需要使用布局模板功能,可以在模板文件开头加上 {__NOLAYOUT__} 字符串。
如果上面的User/add.html 模板文件里面包含有{__NOLAYOUT__},则即使当前开启布局模板,也不会进行布局模板解析。
第二种方式是以当前输出模板为入口的方式
以前面的输出模板为例,这种方式的入口还是在User/add.html 模板,但是我们可以修改下add模板文件的内容,在头部增加下面的布局标签:
- <layout name="layout" />
表示当前模板文件需要使用layout.html 布局模板文件,而布局模板文件的写法和上面第一种方式是一样的。当渲染User/add.html 模板文件的时候,如果读取到layout标签,则会把当前模板的解析内容替换到layout布局模板的{__CONTENT__} 特定字符串。
如果需要使用其他的布局模板,可以改变layout的name属性,例如:
- <layout name="new_layout" />
由于所有include标签引入的文件都支持layout标签,所以,我们可以借助layout标签和include标签相结合的方式实现布局模板的嵌套。例如,上面的例子
- <include file="Public:header" />
- <div id="main" class="main" >
- {__CONTENT__}
- </div>
- <include file="Public:bottom" />
在引入的header和footer模板文件中也可以添加layout标签,例如header模板文件的开头添加如下标签:
- <layout name="menu" />
这样就实现了在头部模板中引用了menu布局模板。
也可以采用两种布局方式的结合,可以实现更加复杂的模板布局以及嵌套功能。
23. 模板继承
模板继承的优势其实是设计基础模板中的区块(block)和子模板中替换这些区块。每个区块由<block></block>标签组成,并且不支持block标签的嵌套。
下面就是基础模板中的一个典型的区块设计(用于设计网站标题):
- <block name="title"><title>网站标题</title></block>
block标签必须指定name属性来标识当前区块的名称,这个标识在当前模板中应该是唯一的,block标签中可以包含任何模板内容,包括其他标签和变量,例如:
- <block name="title"><title>{$web_title}</title></block>
你甚至还可以在区块中加载外部文件:
- <block name="include"><include file="Public:header" /></block>
在子模板中,可以对基础模板中的区块进行重载定义,如果没有重新定义的话,则表示沿用基础模板中的区块定义,如果定义了一个空的区块,则表示删除基础模板中的该区块内容。
24. 原样输出
literal标签(保持原样输出) | |
---|---|
闭合 | 非闭合标签 |
属性 | 无 |
可以使用literal标签来防止模板标签被解析,例如:
- <literal>
- <if condition="$name eq 1 "> value1
- <elseif condition="$name eq 2"/>value2
- <else /> value3
- </if>
- </literal>
上面的if标签被literal标签包含,因此if标签里面的内容并不会被模板引擎解析,而是保持原样输出。
Literal标签还可以用于页面的JS代码外层,确保JS代码中的某些用法和模板引擎不产生混淆。
总之,所有可能和内置模板引擎的解析规则冲突的地方都可以使用literal标签处理。
25. 模板注释
模板支持注释功能,该注释文字在最终页面不会显示,仅供模板制作人员参考和识别。
- {// 这是模板注释内容 }
- {/* 这是模板
- 注释内容*/ }
模板注释支持多行,模板注释在生成编译缓存文件后会自动删除,这一点和Html的注释不同。
26. 引入标签库
格式:<tagLib name="标签库1[,标签库2,…]"/>
可以同时导入多个标签库,用逗号分隔,例如:
- <tagLib name="html"/>
表示在当前模板文件需要引入html标签库。要引入标签库必须确保有Html标签库的定义文件和解析类库(如何扩展这种方式请参考前面的标签库扩展部分)。
引入后,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标签库是系统内置标签库,不能删除定义。
27. 修改定界符
要更改普遍模板的起始标签和结束标签,请使用下面的配置参数:
- TMPL_L_DELIM //模板引擎普通标签开始标记
- TMPL_R_DELIM //模板引擎普通标签结束标记
例如在项目配置文件中增加下面的配置:
- ‘TMPL_L_DELIM‘=>‘‘,
普通模板标签主要用于模板变量输出和模板注释。如果要使用其它功能,请使用XML模板标签。XML模板标签可以用于模板变量输出、文件包含、条件控制、循环输出等功能,而且完全可以自己扩展功能。如果你觉得XML标签无法在正在使用的编辑器里面无法编辑,还可以更改XML标签库的起始和结束标签,请修改下面的配置参数:
- TAGLIB_BEGIN //标签库标签开始标签
- TAGLIB_END //标签库标签结束标记
例如在项目配置文件中增加下面的配置:
- ‘TAGLIB_BEGIN‘=>‘[‘,
- ‘TAGLIB_END‘=>‘]‘,
注意:XML标签和普通标签的定界符不能冲突,否则会导致解析错误。如果你定制了普通表情的定界符,而且默认跳转页面用的是系统默认的话,记得修改下默认跳转模板中的变量定界符。
28. 避免JS混淆
如果使用内置的模板引擎,而且采用默认的标签设置的话,在某些情况下,如果不注意,{$(‘name‘).value} 这样的JS代码很容易被内置模板引擎误解析。
有三个方法可以解决类似的混淆问题:
1、{$(‘name‘).value}改成{ $(‘name‘).value}
因为内置模板引擎的解析规则是"{"后面紧跟"$"符号才会解析变量 因此只要在"{" 和"$"之间添加空格就不会被误解析了
2、使用内置的literal标签包含JS代码
<literal>JS代码</literal> 包含在literal标签中的代码将会直接输出,不进行任何解析
3、定制模板引擎标签的定界符
例如:‘TMPL_L_DELIM‘=>‘<{‘,‘TMPL_R_DELIM‘=>‘}>‘这样就和JS代码区别开来了。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
日志:
日志的处理工作是由系统自动进行的,在开启日志记录的情况下,会记录下允许的日志级别的所有日志信息。其中,为了性能考虑,SQL日志级别必须在调试模式开启下有效,否则就不会记录。
系统的日志记录由核心的Log类完成,提供了多种方式记录了不同的级别的日志信息。
1. 日志级别
EMERG | 严重错误,导致系统崩溃无法使用 |
ALERT | 警戒性错误, 必须被立即修改的错误 |
CRIT | 临界值错误, 超过临界值的错误 |
ERR | 一般性错误 |
WARN | 警告性错误, 需要发出警告的错误 |
NOTICE | 通知,程序可以运行但是还不够完美的错误 |
INFO | 信息,程序输出信息 |
DEBUG | 调试,用于调试信息 |
SQL | SQL语句,该级别只在调试模式开启时有效 |
要开启日志记录,必须在配置中开启LOG_RECORD参数,以及可以在项目配置文件中配置需要记录的日志级别,例如:
- ‘LOG_RECORD‘ => true, // 开启日志记录
- ‘LOG_LEVEL‘ =>‘EMERG,ALERT,CRIT,ERR‘, // 只记录EMERG ALERT CRIT ERR 错误
2. 记录方式
记录方式 | 说明 | 常量标识 |
---|---|---|
SYSTEM | 日志发送到PHP的系统日志记录 | 0 |
日志通过邮件方式发送 | 1 | |
FILE | 日志通过文件方式记录(默认方式) | 3 |
SAPI | 日志通过SAPI方式记录 | 4 |
日志的记录格式:记录时间 访问URL | 日志级别:日志信息
其中的时间显示可以动态配置,默认是采用 [ c ],例如我们可以改成:
- Log::$format = ‘[ Y-m-d H:i:s ]‘;
其格式定义和date函数的用法一致,默认情况下具体的日志信息类似于下面的内容:
- [2012-01-15T18:09:22+08:00] /Index/index|NOTIC: [8] Undefined variable: verify PublicAction.class.php 第 162 行.
- [2012-01-15T18:09:22+08:00] /Index/index | SQL: RunTime:0.214238s SQL = SHOW COLUMNS FROM think_user
- [2012-01-15T18:09:22+08:00] /Index/index | SQL: RunTime:0.039159s SQL = SELECT * FROM `think_user` WHERE ( `account` = ‘admin‘ ) AND ( `status` > 0 ) LIMIT 1
默认采用文件方式记录日志信息,日志文件的命名格式是:年(简写)_月_日.log,例如:
09_10_01.log 表示2009年10月1日的日志文件
可以设置LOG_FILE_SIZE参数来限制日志文件的大小,超过大小的日志会形成备份文件。备份文件的格式是在当前文件名前面加上备份的时间戳,例如:
1189571417-07_09_12.log 备份的日志文件
如果需要使用其他方式记录日志,可以设置LOG_TYPE参数,例如下面设置了采用邮件方式发送日志记录:
- ‘LOG_TYPE‘ =>1, // 采用邮件方式记录日志
- ‘LOG_DEST‘ =>‘[email protected]‘, // 要发送日志的邮箱
- ‘LOG_EXTRA‘ =>‘From: [email protected]‘, // 邮件的发件人设置
3. 手动记录
http://doc.thinkphp.cn/manual/log_record.html
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
错误:
1. 异常处理
和PHP默认的异常处理不同,ThinkPHP抛出的不是单纯的错误信息,而是一个人性化的错误页面。
只有在调试模式下面才能显示具体的错误信息,如果在部署模式下面,你可能看到的是一个统一错误的提示文字,如果你试图在部署模式下访问一个不存在的模块或者操作,会发送404错误。
调试模式下面一旦系统发生严重错误会自动抛出异常,也可以用ThinkPHP定义的throw_exception方法手动抛出异常。
- throw_exception(‘新增失败‘);
- throw_exception(‘信息录入错误‘,‘InfoException‘);
同样也可以使用throw 关键字来抛出异常,下面的写法是等效的:
- throw new ThinkException(‘新增失败‘);
- throw new InfoException(‘信息录入错误‘);
如果需要,我们建议在项目的类库目录下面增加Exception目录用于专门存放异常类库,以更加精确地定位异常。
2. 异常模板
系统内置的异常模板在系统目录的Tpl/think_exception.tpl,可以通过修改系统模板来修改异常页面的显示。也通过设置TMPL_EXCEPTION_FILE 配置参数来修改系统默认的异常模板文件, 例如:
- ‘TMPL_EXCEPTION_FILE‘ => APP_PATH.‘/Public/exception.tpl‘
异常模板中可以使用的异常变量有:
$e[‘file‘]异常文件名
$e[‘line‘] 异常发生的文件行数
$e[‘message‘] 异常信息
$e[‘trace‘] 异常的详细Trace信息
因为异常模板使用的是原生PHP代码,所以还可以支持任何的PHP方法和系统变量使用。
3. 异常显示
抛出异常后通常会显示具体的错误信息,如果不想让用户看到具体的错误信息,可以设置关闭错误信息的显示并设置统一的错误提示信息,例如:
- ‘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所指向的页面不能再使用异常的模板变量了。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------