因为工作需要,要重新捡起远古框架ThinKPHP3.2
https://www.kancloud.cn/manual/thinkphp/1679
基础 安装ThinkPHP3.2 1 composer create-project topthink /thinkphp ThinkPHP32
环境要求
目录结构 1 2 3 4 5 6 www WEB部署目录(或者子目录) ├─index.php 入口文件 ├─README.md README文件 ├─Application 应用目录 ├─Public 资源文件目录 └─ThinkPHP 框架目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ├─ThinkPHP 框架系统目录(可以部署在非web目录下面) │ ├─Common 核心公共函数目录 │ ├─Conf 核心配置目录 │ ├─Lang 核心语言包目录 │ ├─Library 框架类库目录 │ │ ├─Think 核心Think类库包目录 │ │ ├─Behavior 行为类库目录 │ │ ├─Org Org类库包目录 │ │ ├─Vendor 第三方类库目录 │ │ ├─ ... 更多类库目录 │ ├─Mode 框架应用模式目录 │ ├─Tpl 系统模板目录 │ ├─LICENSE.txt 框架授权协议文件 │ ├─logo.png 框架LOGO文件 │ ├─README.txt 框架README文件 │ └─ThinkPHP.php 框架入口文件
控制器 我们可以在自动生成的Application/Home/Controller
目录下面找到一个 IndexController.class.php
文件,这就是默认的Index
控制器文件。
控制器类的命名方式是:控制器名(驼峰法,首字母大写)+Controller
控制器文件的命名方式是:类名+class.php(类文件后缀)
开发规范 使用ThinkPHP开发的过程中应该尽量遵循下列命名规范:
类文件都是以.class.php为后缀(这里是指的ThinkPHP内部使用的类库文件,不代表外部加载的类库文件),使用驼峰法命名,并且首字母大写,例如 DbMysql.class.php
;
类的命名空间地址和所在的路径地址一致,例如 Home\Controller\UserController
类所在的路径应该是 Application/Home/Controller/UserController.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中,一般来说应用的配置文件是自动加载的,加载的顺序是:
惯例配置
->应用配置
->模式配置
->调试配置
->状态配置
->模块配置
->扩展配置
->动态配置
惯例重于配置是系统遵循的一个重要思想,框架内置有一个惯例配置文件(位于ThinkPHP/Conf/convention.php
),按照大多数的使用对常用参数进行了默认配置
应用配置文件也就是调用所有模块之前都会首先加载的公共配置文件(默认位于Application/Common/Conf/config.php
)。
如果使用了普通应用模式之外的应用模式的话,还可以为应用模式(后面会有描述)单独定义配置文件,文件命名规范是: Application/Common/Conf/config_应用模式名称.php
(仅在运行该模式下面才会加载)。
如果开启调试模式的话,则会自动加载框架的调试配置文件(位于ThinkPHP/Conf/debug.php
)和应用调试配置文件(位于Application/Common/Conf/debug.php
)
1 2 3 4 5 6 7 8 define ('APP_STATUS' ,'office' );define ('APP_STATUS' ,'home' );
读取配置 无论何种配置文件,定义了配置文件之后,都统一使用系统提供的C
方法(可以借助Config
单词来帮助记忆)来读取已有的配置。
例如,读取当前的URL模式配置参数:
1 2 3 $model = C ('URL_MODEL' );
动态配置
1 2 C ('DATA_CACHE_TIME' ,60 );
也可以支持二维数组的读取和设置,使用点语法进行操作,如下:
1 2 3 4 C ('USER_CONFIG.USER_TYPE' );C ('USER_CONFIG.USER_TYPE' ,1 );
架构 多层MVC ThinkPHP基于MVC(Model-View-Controller
,模型-视图-控制器)模式,并且均支持多层(multi-Layer)设计。
模型(Model)层 例如在某个项目设计中需要区分数据层、逻辑层、服务层等不同的模型层,我们可以在模块目录下面创建Model、Logic和Service目录,把对用户表的所有模型操作分成三层:
数据层:Model/UserModel
用于定义数据相关的自动验证和自动完成和数据存取接口
逻辑层:Logic/UserLogic
用于定义用户相关的业务逻辑
服务层:Service/UserService
用于定义用户相关的服务接口等
视图(View)层 视图层由模板和模板引擎组成,在模板中可以直接使用PHP代码,模板引擎的设计会在后面讲述,通过驱动也可以支持其他第三方的模板引擎
控制器(Controller)层 ThinkPHP的控制器层由核心控制器 和业务控制器 组成,核心控制器由系统内部的App类完成,负责应用(包括模块、控制器和操作)的调度控制,包括HTTP请求拦截和转发、加载配置等
CBD模式 ThinkPHP引入了全新的CBD(核心Core+行为Behavior+驱动Driver) 架构模式。
Core(核心) ThinkPHP的核心部分包括核心函数库、惯例配置、核心类库(包括基础类和内置驱动及核心行为),这些是ThinkPHP必不可少的部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 ThinkPHP/Common/functions.php ThinkPHP/Conf/convention.php ThinkPHP/Conf/debug.php ThinkPHP/Mode/common.php ThinkPHP/Library/Think ThinkPHP/Library/Behavior ThinkPHP/Library/Think/App.class .php ThinkPHP/Library/Think/Cache.class .php ThinkPHP/Library/Think/Controller.class .php ThinkPHP/Library/Think/Db.class .php ThinkPHP/Library/Think/Dispatcher.class .php ThinkPHP/Library/Think/Exception .class .php ThinkPHP/Library/Think/Hook.class .php ThinkPHP/Library/Think/Log.class .php ThinkPHP/Library/Think/Model.class .php ThinkPHP/Library/Think/Route.class .php ThinkPHP/Library/Think/Storage.class .php ThinkPHP/Library/Think/Template.class .php ThinkPHP/Library/Think/Think.class .php ThinkPHP/Library/Think/View.class .php
Behavior (行为) Behavior目录下面是系统内置的一些行为类库,内置驱动则分布在各个不同的驱动目录下面(参考下面的驱动部分)。
行为(Behavior)是ThinkPHP扩展机制中比较关键的一项扩展,行为既可以独立调用,也可以绑定到某个标签(位)中进行侦听。
行为类的定义方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 namespace Home \Behavior ;class TestBehavior { public function run (&$params ) { if (C ('TEST_PARAM' )) { echo 'RUNTEST BEHAVIOR ' .$params ; } } }
Driver(驱动) 1 2 3 4 5 6 7 ThinkPHP/Library/Think/Cache/Driver ThinkPHP/Library/Think/Db/Driver ThinkPHP/Library/Think/Log/Driver ThinkPHP/Library/Think/Session/Driver ThinkPHP/Library/Think/Storage/Driver ThinkPHP/Library/Think/Template/Driver ThinkPHP/Library/Think/Template/TagLib
自动加载 自动加载的优先级 在实际的应用类库加载过程中,往往会涉及到自动加载的优先级问题,以Test\MyClass
类为例,自动加载的优先顺序如下:
判断是否有注册了Test\MyClass
类库映射,如果有则自动加载类库映射定义的文件;
判断是否存在Library/Test
目录,有则以该目录为初始目录加载;
判断是否有注册Test
根命名空间,有则以注册的目录为初始目录加载;
如果以上都不成立,则以Test
为模块目录进行初始目录加载;
手动加载第三方类库 如果要加载第三方类库,包括不符合命名规范和后缀的类库,以及没有使用命名空间或者命名空间和路径不一致的类库 ,或者你就是想手动加载类库文件,我们都可以通过手动导入的方式加载。
我们可以使用import
方法导入任何类库,用法如下:
1 2 3 4 5 6 7 8 import ("Org.Util.Date" );import ("Home.Util.UserUtil" );import ("@.Util.Array" );import ('Vendor.Zend.Server' );
系统流程 ThinkPHP框架开发的应用的标准执行流程如下:
用户URL请求
调用应用入口文件(通常是网站的index.php
)
载入框架入口文件(ThinkPHP.php
)
记录初始运行时间和内存开销
系统常量判断及定义
载入框架引导类(Think\Think
)并执行Think::start
方法进行应用初始化
设置错误处理机制和自动加载机制
调用Think\Storage
类进行存储初始化(由STORAGE_TYPE
常量定义存储类型)
部署模式下如果存在应用编译缓存文件则直接加载(直接跳转到步骤22)
读取应用模式(由APP_MODE
常量定义)的定义文件(以下以普通模式为例说明)
加载当前应用模式定义的核心文件(普通模式是 ThinkPHP/Mode/common.php
)
加载惯例配置文件(普通模式是 ThinkPHP/Conf/convention.php
)
加载应用配置文件(普通模式是 Application/Common/Conf/config.php
)
加载系统别名定义
判断并读取应用别名定义文件(普通模式是 Application/Common/Conf/alias.php
)
加载系统行为定义
判断并读取应用行为定义文件(普通模式是 Application/Common/Conf/tags.php
)
加载框架底层语言包(普通模式是 ThinkPHP/Lang/zh-cn.php
)
如果是部署模式则生成应用编译缓存文件
加载调试模式系统配置文件(ThinkPHP/Conf/debug.php
)
判断并读取应用的调试配置文件(默认是 Application/Common/Conf/debug.php
)
判断应用状态并读取状态配置文件(如果APP_STATUS
常量定义不为空的话)
检测应用目录结构并自动生成(如果CHECK_APP_DIR
配置开启并且RUNTIME_PATH
目录不存在的情况下)
调用Think\App
类的run
方法启动应用
应用初始化(app_init
)标签位侦听并执行绑定行为
判断并加载动态配置和函数文件
调用Think\Dispatcher::dispatch
方法进行URL请求调度
自动识别兼容URL模式和命令行模式下面的$_SERVER['PATH_INFO']
参数
检测域名部署以及完成模块和控制器的绑定操作(APP_SUB_DOMAIN_DEPLOY
参数开启)
分析URL地址中的PATH_INFO
信息
获取请求的模块信息
检测模块是否存在和允许访问
判断并加载模块配置文件、别名定义、行为定义及函数文件
判断并加载模块的动态配置和函数文件
模块的URL模式判断
模块的路由检测(URL_ROUTER_ON
开启)
PATH_INFO
处理(path_info
)标签位侦听并执行绑定行为
URL后缀检测(URL_DENY_SUFFIX
以及URL_HTML_SUFFIX
处理)
获取当前控制器和操作,以及URL其他参数
URL请求调度完成(url_dispatch
)标签位侦听并执行绑定行为
应用开始(app_begin
)标签位侦听并执行绑定行为
调用SESSION_OPTIONS
配置参数进行Session
初始化(如果不是命令行模式)
根据请求执行控制器方法
如果控制器不存在则检测空控制器是否存在
控制器开始(action_begin
)标签位侦听并执行绑定行为
默认调用系统的ReadHtmlCache
行为读取静态缓存(HTML_CACHE_ON
参数开启)
判断并调用控制器的_initialize
初始化方法
判断操作方法是否存在,如果不存在则检测是否定义空操作方法
判断前置操作方法是否定义,有的话执行
Action参数绑定检测,自动匹配操作方法的参数
如果有模版渲染(调用控制器display方法)
视图开始(view_begin
)标签位侦听并执行绑定行为
调用Think\View
的fetch
方法解析并获取模版内容
自动识别当前主题以及定位模版文件
视图解析(view_parse
)标签位侦听并执行绑定行为
默认调用内置ParseTemplate
行为解析模版(普通模式下面)
模版引擎解析模版内容后生成模版缓存
模版过滤替换(template_filter
)标签位侦听并执行绑定行为
默认调用系统的ContentReplace
行为进行模版替换
输出内容过滤(view_filter
)标签位侦听并执行绑定行为
默认调用系统的WriteHtmlCache
行为写入静态缓存(HTML_CACHE_ON
参数开启)
调用Think\View
类的render
方法输出渲染内容
视图结束(view_end
)标签位侦听并执行绑定行为
判断后置操作方法是否定义,有的话执行
控制器结束(action_end
)标签位侦听并执行绑定行为
应用结束(app_end
)标签位侦听并执行绑定行为
执行系统的ShowPageTrace
行为(SHOW_PAGE_TRACE
参数开启并且不是AJAX
请求)
日志信息存储写入
如果是部署模式下面的第二次请求的话,上面的流程中的步骤10~21是可以省略的。
路由 规则路由 规则表达式 1 2 3 4 'my' => 'Member/myinfo' , 'blog/:id' => 'Blog/read' , 'new/:year/:month/:day' =>'News/read' , ':user/:blog_id' =>'Blog/read' ,
实例说明 假设我们定义了News控制器如下(代码实现仅供参考):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <?php namespace Home \Controller ;use Think \Controller ;class NewsController extends Controller { public function read ( ) { $New = M ('New' ); if (isset ($_GET ['id' ])) { $data = $New ->find ($_GET ['id' ]); } elseif (isset ($_GET ['name' ])){ $data = $New ->getByName ($_GET ['name' ]); } $this ->data = $data ; $this ->display (); } public function archive ( ) { $New = M ('New' ); $year = $_GET ['year' ]; $month = $_GET ['month' ]; $begin_time = strtotime ($year . $month . "01" ); $end_time = strtotime ("+1 month" , $begin_time ); $map ['create_time' ] = array (array ('gt' ,$begin_time ),array ('lt' ,$end_time )); $map ['status' ] = 1 ; $list = $New ->where ($map )->select (); $this ->list = $list ; $this ->display (); } }
定义路由规则如下:
1 2 3 4 5 6 'URL_ROUTER_ON' => true , 'URL_ROUTE_RULES' => array ( 'new/:id\d' => 'News/read' , 'new/:name' => 'News/read' , 'new/:year\d/:month\d' => 'News/archive' , ),
控制器 前置和后置操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?php namespace Home \Controller ;use Think \Controller ;class IndexController extends Controller { public function _before_index ( ) { echo 'before<br/>' ; } public function index ( ) { echo 'index<br/>' ; } public function _after_index ( ) { echo 'after<br/>' ; } }
伪静态 URL伪静态通常是为了满足更好的SEO 效果,ThinkPHP支持伪静态URL设置,可以通过设置URL_HTML_SUFFIX
参数随意在URL的最后增加你想要的静态后缀,而不会影响当前操作的正常执行。例如,我们设置:
1 'URL_HTML_SUFFIX' =>'shtml'
默认情况下,伪静态的设置为html,如果我们设置伪静态后缀为空,
则可以支持所有的静态后缀,并且会记录当前的伪静态后缀到常量 __EXT__
,但不会影响正常的页面访问。
如果希望支持多个伪静态后缀,可以直接设置如下:
1 2 'URL_HTML_SUFFIX' => 'html|shtml|xml'
可以设置禁止访问的URL后缀,例如:
1 'URL_DENY_SUFFIX' => 'pdf|ico|png|gif|jpg' ,
注意: URL_DENY_SUFFIX的优先级比URL_HTML_SUFFIX要高。
URL生成 为了配合所使用的URL模式,我们需要能够动态的根据当前的URL设置生成对应的URL地址,为此,ThinkPHP内置提供了U 方法,用于URL的动态生成,可以确保项目在移植过程中不受环境的影响。
U(‘地址表达式’,[‘参数’],[‘伪静态后缀’],[‘显示域名’])
1 2 3 U ('User/add' ) U ('Blog/read?id=1' ) U ('Admin/User/select' )
AJAX返回 ThinkPHP可以很好的支持AJAX请求,系统的\Think\Controller
类提供了ajaxReturn
方法用于AJAX
调用后返回数据给客户端。并且支持JSON
、JSONP
、XML
和EVAL
四种方式给客户端接受数据,并且支持配置其他方式的数据格式返回。
1 2 3 4 5 6 7 $data = 'ok' ;$this ->ajaxReturn ($data );$data ['status' ] = 1 ;$data ['content' ] = 'content' ;$this ->ajaxReturn ($data );
默认配置采用JSON格式返回数据(通过配置DEFAULT_AJAX_RETURN进行设置),我们可以指定格式返回,例如:
1 2 3 4 $data ['status' ] = 1 ;$data ['content' ] = 'content' ;$this ->ajaxReturn ($data ,'xml' );
跳转和重定向 页面跳转 1 2 3 4 5 6 7 8 9 $User = M ('User' ); $result = $User ->add ($data ); if ($result ){ $this ->success ('新增成功' , '/User/index' ); } else { $this ->error ('新增失败' ); }
跳转地址是可选的,success
方法的默认跳转地址是$_SERVER["HTTP_REFERER"]
,error
方法的默认跳转地址是javascript:history.back(-1)
;。
默认的等待时间success方法是1秒,error方法是3秒
重定向 1 2 $this ->redirect ('New/category' , array ('cate_id' => 2 ), 5 , '页面跳转中...' );
输入变量 获取变量 1 2 3 4 5 $id = $_GET ['id' ]; $name = $_POST ['name' ]; $value = $_SESSION ['var' ]; $name = $_COOKIE ['name' ]; $file = $_SERVER ['PHP_SELF' ];
但是我们不建议直接使用传统方式获取,因为没有统一的安全处理机制,后期如果调整的话,改起来会比较麻烦。所以,更好的方式是在框架中统一使用I 函数进行变量获取和过滤。
I(‘变量类型.变量名/修饰符’,[‘默认值’],[‘过滤方法或正则’],[‘额外数据源’])
变量修饰符
1 2 3 I ('get.id/d' ); I ('post.name/s' ); I ('post.ids/a' );
请求类型
IS_GET
:判断是否是GET方式提交
IS_POST
:判断是否是POST方式提交
IS_PUT
:判断是否是PUT方式提交
IS_DELETE
:判断是否是DELETE方式提交
IS_AJAX
:判断是否是AJAX提交
REQUEST_METHOD
:当前提交类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class UserController extends Controller { public function update ( ) { if (IS_POST) { $User = M ('User' ); $User ->create (); $User ->save (); $this ->success ('保存完成' ); } else { $this ->error ('非法请求' ); } } }
空操作 空操作是指系统在找不到请求的操作方法的时候,会定位到空操作(_empty
)方法来执行,利用这个机制,我们可以实现错误页面和一些URL的优化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php namespace Home \Controller ;use Think \Controller ;class CityController extends Controller { public function _empty ($name ) { $this ->city ($name ); } protected function city ($name ) { echo '当前城市' . $name ; } }
模型 模型定义 1 2 3 4 5 6 7 8 9 10 11 12 13 <?php namespace Home \Model ;use Think \Model ;class CategoryModel extends Model { protected $tableName = 'categories' ; protected $tablePrefix = 'top_' ; protected $trueTableName = 'top_categories' ; protected $dbName = 'top' ; }
模型实例化 D方法实例化 1 2 3 4 5 6 <?php $User = D ('User' );$User ->select ();
切换数据库 如果我们已经在项目配置中定义了其他的数据库连接信息,例如:
1 2 3 4 5 6 7 8 9 10 11 'DB_CONFIG1' = array ( 'db_type' => 'mysql' , 'db_user' => 'root' , 'db_pwd' => '1234' , 'db_host' => 'localhost' , 'db_port' => '3306' , 'db_name' => 'thinkphp' ), 'DB_CONFIG2' => 'mysql://root:1234@localhost:3306/thinkphp' ;
我们就可以直接在db方法中调用配置进行连接了:
1 2 $this ->db (1 ,"DB_CONFIG1" )->query ("查询SQL" );$this ->db (2 ,"DB_CONFIG2" )->query ("查询SQL" );
如果切换数据库之后,数据表和当前不一致的话,可以使用table方法指定要操作的数据表:
1 $this ->db (1 )->table ("top_user" )->find ();
连贯操作 lock Lock
方法是用于数据库的锁机制,如果在查询或者执行操作的时候使用:
fetchSql fetchSql
用于直接返回SQL而不是执行查询,适用于任何的CURD操作方法。 例如:
1 $result = M ('User' )->fetchSql (true )->find (1 );
token token
方法可用于临时关闭令牌验证,例如:
1 $model ->token (false )->create ();
strict strict
为3.2.3新增连贯操作,用于设置数据写入和查询是否严格检查是否存在字段。默认情况下不合法数据字段自动删除,如果设置了严格检查则会抛出异常。 例如:
1 $model ->strict (true )->add ($data );
index index
方法用于数据集的强制索引操作,例如:
1 $Model ->index ('user' )->select ();
查询语言 SQL查询 QUERY方法 1 2 $Model = new \Think\Model () $Model ->query ("select * from think_user where status=1" );
EXECUTE方法 1 2 $Model = new \Think\Model () $Model ->execute ("update think_user set name='thinkPHP' where status=1" );
动态查询 getBy动态查询 1 2 3 $user = $User ->getByName ('liu21st' );$user = $User ->getByEmail ('liu21st@gmail.com' );$user = $User ->getByAddress ('中国深圳' );
getFieldBy动态查询 1 $userId = $User ->getFieldByName ('liu21st' ,'id' );
子查询 1、使用select方法 当select方法的参数为false的时候,表示不进行查询只是返回构建SQL,例如:
1 2 $subQuery = $model ->field ('id,name' )->table ('tablename' )->group ('field' )->where ($where )->order ('status' )->select (false );
当select方法传入false参数的时候,表示不执行当前查询,而只是生成查询SQL。
2、使用buildSql方法
1 $subQuery = $model ->field ('id,name' )->table ('tablename' )->group ('field' )->where ($where )->order ('status' )->buildSql ();
调用buildSql方法后不会进行实际的查询操作,而只是生成该次查询的SQL语句(为了避免混淆,会在SQL两边加上括号),然后我们直接在后续的查询中直接调用。
1 2 $model ->table ($subQuery .' a' )->where ()->order ()->select ()
自动验证 动态验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 $rules = array ( array ('verify' ,'require' ,'验证码必须!' ), array ('name' ,'' ,'帐号名称已经存在!' ,0 ,'unique' ,1 ), array ('value' ,array (1 ,2 ,3 ),'值的范围不正确!' ,2 ,'in' ), array ('repassword' ,'password' ,'确认密码不正确' ,0 ,'confirm' ), array ('password' ,'checkPwd' ,'密码格式不正确' ,0 ,'function' ), ); $User = M ("User" ); if (!$User ->validate ($rules )->create ()){ exit ($User ->getError ()); } else { }
参数绑定 手动绑定 1 2 3 $Model = M ('User' );$where ['name' ] = ':name' ;$list = $Model ->where ($where )->bind (':name' ,I ('name' ))->select ();
目前不支持 ?
方式进行占位符,统一使用 :var
方式进行占位符,驱动内部会自动进行处理。
自动绑定 1 2 3 4 $Model = M ('User' );$Model ->name = 'thinkphp' ;$Model ->email = 'thinkphp@qq.com' ;$Model ->add ();
关联模型 关联关系 1 2 3 一对一关联 :ONE_TO_ONE,包括HAS_ONE 和 BELONGS_TO 一对多关联 :ONE_TO_MANY,包括HAS_MANY 和 BELONGS_TO 多对多关联 :MANY_TO_MANY
HAS_ONE HAS_ONE
关联表示当前模型拥有一个子对象,例如,每个员工都有一个人事档案。我们可以建立一个用户模型UserModel,并且添加如下关联定义:
1 2 3 4 5 6 7 namespace Home \Model ;use Think \Model \RelationModel ;class UserModel extends RelationModel { protected $_link = array ( 'Profile' => self ::HAS_ONE , ); }
BELONGS_TO Belongs_to
关联表示当前模型从属于另外一个父对象,例如每个用户都属于一个部门。我们可以做如下关联定义。
1 'Dept' => self ::BELONGS_TO
HAS_MANY HAS_MANY
关联表示当前模型拥有多个子对象,例如每个用户有多篇文章,我们可以这样来定义:
1 'Article' => self ::HAS_MANY
MANY_TO_MANY MANY_TO_MANY
关联表示当前模型可以属于多个对象,而父对象则可能包含有多个子对象,通常两者之间需要一个中间表类约束和关联。例如每个用户可以属于多个组,每个组可以有多个用户:
1 'Group' => self ::MANY_TO_MANY
关联删除 1 2 3 4 $result = $User ->relation (true )->delete ("3" );$result = $User ->relation ("Profile" )->delete ("3" );
高级模型 字段过滤 1 2 3 protected $_filter = array ( '过滤的字段' =>array ('写入过滤规则' ,'读取过滤规则' ,是否传入整个数据对象), );
序列化字段 1 2 3 protected $serializeField = array ( 'info' => array ('name' , 'email' , 'address' ), );
文本字段 1 Protected $blobFields = array ('content' );
只读字段 1 protected $readonlyField = array ('name' , 'email' );
悲观锁( Pessimistic Locking )
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。 通常是使用for update子句来实现悲观锁机制。
ThinkPHP支持悲观锁机制,默认情况下,是关闭悲观锁功能的,要在查询和更新的时候启用悲观锁功能,可以通过使用之前提到的查询锁定方法,例如:
1 $User ->lock (true )->save ($data );
乐观锁( Optimistic Locking )
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。 如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情况将导致怎样的后果。乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。
ThinkPHP也可以支持乐观锁机制,要启用乐观锁,只需要继承高级模型类并定义模型的optimLock属性,并且在数据表字段里面增加相应的字段就可以自动启用乐观锁机制了。默认的optimLock属性是lock_version,也就是说如果要在User表里面启用乐观锁机制,只需要在User表里面增加lock_version字段,如果有已经存在的其它字段作为乐观锁用途,可以修改模型类的optimLock属性即可。如果存在optimLock属性对应的字段,但是需要临时关闭乐观锁机制,把optimLock属性设置为false就可以了。
数据分表 对于大数据量的应用,经常会对数据进行分表 ,有些情况是可以利用数据库的分区 功能,但并不是所有的数据库或者版本都支持,因此我们可以利用ThinkPHP内置的数据分表功能来实现。帮助我们更方便的进行数据的分表和读取操作。
和数据库分区功能不同,内置的数据分表功能需要根据分表规则手动创建相应的数据表。 在需要分表的模型中定义partition
属性即可。
1 2 3 4 5 6 protected $partition = array ( 'field' => 'name' , 'type' => 'md5' , 'expr' => 'name' , 'num' => 'name' , );
定义好了分表属性后,我们就可以来进行CURD操作了,唯一不同的是,获取当前的数据表不再使用getTableName
方法,而是使用getPartitionTableName
方法,而且必须传入当前的数据。然后根据数据分析应该实际操作哪个数据表。因此,分表的字段值必须存在于传入的数据中,否则会进行联合查询。
Mongo模型 Mongo
模型是专门为Mongo数据库驱动而支持的Model扩展,如果需要操作Mongo数据库的话,自定义的模型类必须继承Think\Model\MongoModel
。
Mongo模型为操作Mongo数据库提供了更方便的实用功能和查询用法,包括:
对MongoId
对象和非对象主键的全面支持;
保持了动态追加字段的特性;
数字自增字段的支持;
执行SQL
日志的支持;
字段自动检测的支持;
查询语言的支持;
MongoCode
执行的支持;
视图 模板赋值 1 2 3 4 $array ['name' ] = 'thinkphp' ;$array ['email' ] = 'liu21st@gmail.com' ;$array ['phone' ] = '12335678' ;$this ->assign ($array );
模板渲染
获取模板地址
T([资源://][模块@][主题/][控制器/]操作,[视图分层])
1 2 3 4 5 6 7 8 9 10 11 12 13 14 T ('Public/menu' );T ('blue/Public/menu' );T ('Public/menu' ,'Tpl' );T ('Public/menu' );T ('Public/menu' );T ('Admin@Public/menu' );T ('Extend://Admin@Public/menu' );
调试 调试模式 1 2 3 4 5 6 7 <?php define ('APP_DEBUG' , true );define ('APP_PATH' , './Application/' );require './ThinkPHP/ThinkPHP.php' ;
异常处理 1 2 3 E ('新增失败' );E ('信息录入错误' ,25 );
异常模板中可以使用的异常变量有:
1 2 3 4 $e ['file' ]异常文件名$e ['line' ] 异常发生的文件行数$e ['message' ] 异常信息$e ['trace' ] 异常的详细Trace信息
日志记录 默认情况下只是在调试模式记录日志,要在部署模式开启日志记录,必须在配置中开启LOG_RECORD
参数,以及可以在应用配置文件中配置需要记录的日志级别,例如:
1 2 'LOG_RECORD' => true , 'LOG_LEVEL' =>'EMERG,ALERT,CRIT,ERR' ,
日志级别
EMERG 严重错误,导致系统崩溃无法使用
ALERT 警戒性错误, 必须被立即修改的错误
CRIT 临界值错误, 超过临界值的错误
ERR 一般性错误
WARN 警告性错误, 需要发出警告的错误
NOTICE 通知,程序可以运行但是还不够完美的错误
INFO 信息,程序输出信息
DEBUG 调试,用于调试信息
SQL SQL语句,该级别只在调试模式开启时有效
手动记录
Log::record()
- 记录日志信息到内存
Log::save()
- 把保存在内存中的日志信息(用指定的记录方式)写入
Log::write()
- 实时写入一条日志信息
1 2 Think\Log ::record ('测试日志信息,这是警告级别' ,'WARN' ,true );Think\Log ::write ('测试日志信息,这是警告级别,并且实时写入' ,'WARN' );
页面Trace 1 2 'SHOW_PAGE_TRACE' =>true ,
页面Trace的选项卡是可以定制和扩展的,默认的配置为:
1 2 3 4 5 6 7 8 'TRACE_PAGE_TABS' =>array ( 'base' =>'基本' , 'file' =>'文件' , 'think' =>'流程' , 'error' =>'错误' , 'sql' =>'SQL' , 'debug' =>'调试' )
Trace方法 页面Trace
只能用于有页面输出的情况,但是trace
方法可以用在任何情况,而且trace
方法可以用于AJAX
等操作。
1 trace ('变量' ,'标签' ,'级别' ,'是否记录日志' )
断点调试 凭借强大的页面Trace
信息功能支持,ThinkPHP可以支持断点调试功能。 我们只需要在不同的位置对某个变量进行trace
输出即可,例如:
1 2 3 4 5 $blog = D ("Blog" );$vo = $blog ->create ();trace ($vo ,'create vo' );$vo = $blog ->find ();trace ($vo ,'find vo' );
性能调试 开发过程中,有些时候为了测试性能,经常需要调试某段代码的运行时间或者内存占用开销,系统提供了G方法可以很方便的获取某个区间的运行时间和内存占用情况。 例如:
1 2 3 4 5 6 G ('begin' );G ('end' );echo G ('begin' ,'end' ).'s' ;
默认的统计精度是小数点后4位,如果觉得这个统计精度不够,还可以设置例如:
如果你的环境支持内存占用统计的话,还可以使用G方法进行区间内存开销统计(单位为kb),例如:
1 echo G ('begin' ,'end' ,'m' ).'kb' ;
错误调试 如果需要我们可以使用E方法输出错误信息并中断执行,例如:
模型调试 调试执行的SQL语句 在模型操作中 ,为了更好的查明错误,经常需要查看下最近使用的SQL语句,我们可以用getLastsql
方法来输出上次执行的sql语句。
1 2 3 4 5 $User = M ("User" ); $User ->find (1 );echo $User ->getLastSql ();echo $User ->_sql ();
调试数据库错误信息 1 2 3 4 5 $User = M ("User" ); $result = $User ->find (1 );if (false === $result ){ echo $User ->getDbError (); }
缓存 数据缓存 缓存初始化 1 2 S (array ('type' =>'xcache' ,'expire' =>60 ));
缓存设置
缓存读取
缓存删除
对象方式操作缓存 1 2 3 4 $cache = S (array ('type' =>'xcache' ,'prefix' =>'think' ,'expire' =>600 ));$cache ->name = 'value' ; $value = $cache ->name; unset ($cache ->name);
缓存队列 数据缓存可以支持缓存队列,简单的说就是可以限制缓存的数量,只需要在初始化的时候指定length
参数:
1 S (array ('type' =>'xcache' ,'length' =>100 ,'expire' =>60 ));
快速缓存 如果你的存储数据没有有效期的需求,那么系统还提供了一个快速缓存方法F可以用来更快的操作。
F方法可以支持不同的存储类型,如果是文件类型的话,默认保存在DATA_PATH目录下面。
1 2 3 4 5 6 7 8 9 10 F ('data' ,$Data );F ('data' ,$Data ,TEMP_PATH);$Data = F ('data' );F ('data' ,NULL );
F方法支持自动创建缓存子目录,在DATA_PATH
目录下面缓存data
数据,如果User子目录不存在,则自动创建:
查询缓存 1 2 3 4 $Model ->cache (true )->where ('status=1' )->select ();$Model ->cache ('cache_name' )->select ();
指定key的方式会让查询缓存更加高效。
静态缓存 要使用静态缓存功能,需要开启HTML_CACHE_ON
参数,并且使用HTML_CACHE_RULES
配置参数设置静态缓存规则文件 。
静态规则定义 1 2 3 4 5 6 7 8 9 'HTML_CACHE_ON' => true , 'HTML_CACHE_TIME' => 60 , 'HTML_FILE_SUFFIX' => '.shtml' , 'HTML_CACHE_RULES' => array ( '静态地址' => array ('静态规则' , '有效期' , '附加规则' ), '静态地址' => '静态规则' , )
安全 输入过滤 使用I函数过滤 使用系统内置的I函数是避免输入数据出现安全隐患的重要手段,I函数默认的过滤方法是htmlspecialchar
s,如果我们需要采用其他的方法进行安全过滤,有两种方式:
如果是全局的过滤方法,那么可以设置DEFAULT_FILTER,例如:
1 'DEFAULT_FILTER' => 'strip_tags' ,
当然,我们也可以设置多个过滤方法,例如:
1 'DEFAULT_FILTER' => 'strip_tags,stripslashes' ,
如果是仅需要对个别数据采用特殊的过滤方法,可以在调用I函数的时候传入过滤方法,例如:
1 2 I ('post.id' , 0 , 'intval' ); I ('get.title' , '' , 'strip_tags' );
写入数据过滤 1 $this ->data ($data )->filter ('strip_tags' )->add ();
表单合法性检测 配置insertFields
和 updateFields
属性 1 2 3 4 5 6 7 8 9 <?php namespace Home \Model ;class UserModel extends \Think \Model { protected $insertFields = array ('account' ,'password' ,'nickname' ,'email' ); protected $updateFields = array ('nickname' ,'email' ); }
直接调用field方法 1 M ('User' )->field ('account,password,nickname,email' )->create ();
表单令牌 要启用表单令牌功能,需要配置行为绑定,在应用或者模块的配置目录下面的行为定义文件tags.php
中,添加:
1 2 3 4 5 6 return array ( 'view_filter' => array ('Behavior\TokenBuild' ), );
表示在view_filter
标签位置执行表单令牌检测行为。
1 2 3 4 'TOKEN_ON' => true , 'TOKEN_NAME' => '__hash__' , 'TOKEN_TYPE' => 'md5' , 'TOKEN_RESET' => true ,
如果开启表单令牌验证功能,系统会自动在带有表单的模板文件里面自动生成以TOKEN_NAME
为名称的隐藏域, 其值则是TOKEN_TYPE
方式生成的哈希字符串,用于实现表单的自动令牌验证。
如果个别页面输出不希望进行表单令牌验证,可以在控制器中的输出方法之前动态关闭表单令牌验证,例如:
1 2 C ('TOKEN_ON' ,false );$this ->display ();
模型类在创建数据对象的同时会自动进行表单令牌验证操作,如果你没有使用create方法创建数据对象的话,则需要手动调用模型的autoCheckToken
方法进行表单令牌验证。 如果返回false
,则表示表单令牌验证错误。例如:
1 2 3 4 5 $User = M ("User" ); if (!$User ->autoCheckToken ($_POST )){}
防止SQL注入
查询条件尽量使用数组方式,这是更为安全的方式;
如果不得已必须使用字符串查询条件,使用预处理机制
使用自动验证和自动完成机制进行针对应用的自定义过滤;
如果环境允许,尽量使用PDO方式,并使用参数绑定。
查询条件预处理 where方法使用字符串条件的时候,支持预处理(安全过滤),并支持两种方式传入预处理参数,例如:
1 2 3 $Model ->where ("id=%d and username='%s' and xx='%f'" ,array ($id ,$username ,$xx ))->select ();$Model ->where ("id=%d and username='%s' and xx='%f'" ,$id ,$username ,$xx )->select ();
目录安全文件 为了避免某些服务器开启了目录浏览权限后可以直接在浏览器输入URL地址查看目录,系统默认开启了目录安全文件机制,会在自动生成目录的时候生成空白的index.html
文件, 当然安全文件的名称可以设置,例如你想给安全文件定义为default.html
可以在入口文件中添加:
1 2 3 define ('DIR_SECURE_FILENAME' , 'default.html' );define ('APP_PATH' ,'./Application/' );require './ThinkPHP/ThinkPHP.php' ;
还可以支持多个安全文件写入,例如你想同时写入index.html
和index.htm
两个文件,以满足不同的服务器部署环境,可以这样定义:
1 define ('DIR_SECURE_FILENAME' , 'index.html,index.htm' );
默认的安全文件只是写入一个空白字符串,如果需要写入其他内容,可以通过DIR_SECURE_CONTENT
参数来指定,例如:
1 define ('DIR_SECURE_CONTENT' , 'deny Access!' );
注意:目录安全文件仅在第一次生成模块目录的时候生成。如果是3.2.1版本以上,则可以调用代码生成,例如:
1 2 \Think\Build ::buildDirSecure ($dirs );
上传安全 网站的上传功能也是一个非常容易被攻击的入口,所以对上传功能的安全检查是尤其必要的。
系统提供的上传类Think\Upload
提供了安全方面的支持,包括对文件后缀、文件类型、文件大小以及上传图片文件的合法性检查,确保你已经在上传操作中启用了这些合法性检查。
防止XSS攻击
XSS(跨站脚本攻击) 可以用于窃取其他用户的Cookie信息,要避免此类问题,可以采用如下解决方案:
直接过滤所有的JavaScript脚本;
转义Html元字符,使用htmlentities、htmlspecialchars等函数;
系统的扩展函数库提供了XSS安全过滤的remove_xss方法;
新版对URL访问的一些系统变量已经做了XSS处理。
部署 PATH_INFO支持
如果发生在本地测试正常,但是一旦部署到服务器环境后会发生只能访问首页的情况,很有可能是你的服务器或者空间不支持PATH_INFO
所致。
如果你的环境没有任何对应的系统变量,那么可以封装一个获取方法,例如:
1 2 3 4 function get_path_info ( ) { return $path ; }
然后我们修改下URL_PATHINFO_FETCH参数的配置值,改为:
1 'URL_PATHINFO_FETCH' => ':get_path_info'
URL重写 [ Apache ]
httpd.conf配置文件中加载了mod_rewrite.so模块
AllowOverride None 将None改为 All
把下面的内容保存为.htaccess文件放到应用入口文件的同级目录下
1 2 3 4 5 6 <IfModule mod_rewrite.c> RewriteEngine on RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] </IfModule>
[ Nginx ] 在Nginx低版本中,是不支持PATHINFO的,但是可以通过在Nginx.conf中配置转发规则实现:
1 2 3 4 5 6 location / { if (!-e $request_filename ) { rewrite ^(.*)$ /index.php?s=$1 last; break ; } }
如果你的ThinkPHP安装在二级目录,Nginx的伪静态方法设置如下,其中youdomain是所在的目录名称。
1 2 3 4 5 location /youdomain/ { if (!-e $request_filename ){ rewrite ^/youdomain/(.*)$ /youdomain/index.php?s=$1 last; } }
替换入口
我们的建议是在生产环境中关闭调试模式后生成Lite文件。 注意,目前SAE平台不支持直接生成Lite文件。
生成Lite文件 要生成Lite文件,需要在入口文件中增加常量定义:
1 define ('BUILD_LITE_FILE' ,true );
默认情况下,再次运行后会在Runtime目录下面生成一个lite.php文件。
如果你需要修改Lite文件的位置或者名称,可以在应用配置文件中增加配置如下:
1 'RUNTIME_LITE_FILE' => APP_PATH.'lite.php'
配置后,生成的Lite文件的位置为 APP_PATH.'lite.php'
Lite文件的编译文件内容是系统默认的,如果希望改变或者增加其他的编译文件的话,可以在外部定义编译列表文件, 例如: 我们在应用配置目录下面增加lite.php定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 return array ( THINK_PATH.'Common/functions.php' , COMMON_PATH.'Common/function.php' , CORE_PATH . 'Think' .EXT, CORE_PATH . 'Hook' .EXT, CORE_PATH . 'App' .EXT, CORE_PATH . 'Dispatcher' .EXT, CORE_PATH . 'Model' .EXT, CORE_PATH . 'Log' .EXT, CORE_PATH . 'Log/Driver/File' .EXT, CORE_PATH . 'Route' .EXT, CORE_PATH . 'Controller' .EXT, CORE_PATH . 'View' .EXT, CORE_PATH . 'Storage' .EXT, CORE_PATH . 'Storage/Driver/File' .EXT, CORE_PATH . 'Exception' .EXT, BEHAVIOR_PATH . 'ParseTemplateBehavior' .EXT, BEHAVIOR_PATH . 'ContentReplaceBehavior' .EXT, );
所有在lite.php文件中定义的文件都会纳入Lite文件的编译缓存中。你还可以对生成的lite文件进行修改。
如果你修改了框架文件和应用函数和配置文件的话,需要删除Lite文件重新生成。
替换入口 Lite文件生成后,就可以把原来的应用入口文件中的框架入口文件修改如下:
1 2 3 require './ThinkPHP/ThinkPHP.php' ;require './Runtime/lite.php' ;
替换应用入口文件 如果你的入口文件没有其他代码和逻辑的话,还可以直接把lite.php
文件作为应用的入口文件访问。 把lite.php
文件复制到应用入口文件的相同目录,并直接改名为index.php
即可和原来一样正常访问(原来的应用入口文件可以备份以备用于重新生成Lite文件的时候使用)。
注意:如果你的环境或者目录位置发生变化,以及更改了核心框架和应用函数、配置等文件后,则需要重新生成Lite文件。
专题 session初始化设置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 session ('name' ,'value' ); session ('user.user_id' ,10 ); $value = session ('name' );$value = session ();$value = session ('user.user_id' );session ('name' ,null ); session ('user.user_id' ,null ); session (null ); session ('?name' );session ('?user.user_id' );session ('[操作名]' );session ('[pause]' ); session ('[start]' ); session ('[destroy]' ); session ('[regenerate]' );
Cookie支持 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 cookie ('name' ,'value' ); cookie ('name' ,'value' ,3600 ); cookie ('name' ,'value' ,array ('expire' =>3600 ,'prefix' =>'think_' ))cookie ('name' ,'value' ,'expire=3600&prefix=think_' )$value = cookie ('name' );$value = $_COOKIE ['name' ];$value = $_COOKIE ['前缀+name' ];$value = cookie ();$value = $_COOKIE ;cookie ('name' ,null );cookie (null ); cookie (null ,'think_' );
数据分页 利用Page类和limit方法分页 1 2 3 4 5 6 7 8 9 $User = M ('User' ); $count = $User ->where ('status=1' )->count ();$Page = new \Think\Page ($count ,25 );$show = $Page ->show ();$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方法的实现分页 1 2 3 4 5 6 7 8 9 $User = M ('User' ); $list = $User ->where ('status=1' )->order ('create_time' )->page ($_GET ['p' ].',25' )->select ();$this ->assign ('list' ,$list );$count = $User ->where ('status=1' )->count ();$Page = new \Think\Page ($count ,25 );$show = $Page ->show ();$this ->assign ('page' ,$show );$this ->display ();
常见问题
[Composer\Downloader\TransportException]
Your configuration does not allow connections to http://packagi st.phpcomposer.com/packages.json. See https://getcomposer.org/d oc/06-config.md#secure-http for details.
解决:composer国内镜像不能使用