Thinkphp5框架学习

前言

将近一个月的期末预习,憋死了,昨天考完,算是半解放了,终于又可以愉快的玩耍了,就在昨天刚考完java出考场,就出了一个thinkphp5的牛逼RCE,12月份刚出了一个洞,现在又出了一个,想着复习完后要学一学这个框架,下面是学习笔记。

快速入门:https://www.kancloud.cn/thinkphp/thinkphp5_quickstart/478280
完全开发手册:http://www.php.cn/course/165.html

基础

安装

Composer安装和更新

1
2
3
4
5
6
7
8
9
curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

web目录
composer create-project topthink/think=5.0.* tp5 --prefer-dist

如果之前使用Composer安装的话,首先切换到你的tp5目录,然后使用下面的命令更新框架到最新版本(注意因为缓存关系,composer不一定是及时更新的):

composer update

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
tp5
├─application 应用目录
├─extend 扩展类库目录(可定义)
├─public 网站对外访问目录
├─runtime 运行时目录(可定义)
├─vendor 第三方类库目录(Composer)
├─thinkphp 框架核心目录
├─build.php 自动生成定义文件(参考)
├─composer.json Composer定义文件
├─LICENSE.txt 授权说明文件
├─README.md README 文件
├─think 命令行工具入口

image.png

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
├─application           应用目录(可设置)
│ ├─index 模块目录(可更改)
│ │ ├─config.php 模块配置文件
│ │ ├─common.php 模块公共文件
│ │ ├─controller 控制器目录
│ │ ├─model 模型目录
│ │ └─view 视图目录
│ │
│ ├─command.php 命令行工具配置文件
│ ├─common.php 应用公共文件
│ ├─config.php 应用配置文件
│ ├─tags.php 应用行为扩展定义文件
│ ├─database.php 数据库配置文件
│ └─route.php 路由配置文件


核心框架目录的结构如下:
├─thinkphp 框架系统目录
│ ├─lang 语言包目录
│ ├─library 框架核心类库目录
│ │ ├─think think 类库包目录
│ │ └─traits 系统 traits 目录
│ ├─tpl 系统模板目录
│ │
│ ├─.htaccess 用于 apache 的重写
│ ├─.travis.yml CI 定义文件
│ ├─base.php 框架基础文件
│ ├─composer.json composer 定义文件
│ ├─console.php 控制台入口文件
│ ├─convention.php 惯例配置文件
│ ├─helper.php 助手函数文件(可选)
│ ├─LICENSE.txt 授权说明文件
│ ├─phpunit.xml 单元测试配置文件
│ ├─README.md README 文件
│ └─start.php 框架引导文件

二、URL跟路由

URL访问

1
2
3
4
5
6
URL访问
ThinkPHP采用单一入口模式访问应用,对应用的所有请求都定向到应用的入口文件,系统会从URL参数中解析当前请求的模块、控制器和操作,下面是一个标准的URL访问格式:

http://server/module/controller/action/param/value/...)
http://domainName/index.php/模块/控制器/操作方法
其中index.php就称之为应用的入口文件(注意入口文件可以被隐藏,后面会提到)。

三、请求和响应

1)请求对象

参数传入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

namespace app\index\controller;

class Index
{
public function index()
{
return 'index';
}

public function hello($name = 'World')
{
return 'Hello,' . $name . '!';
}
}

http://tp5.com/index.php/index/index/hello

隐藏入口

1
2
开启apapche2重写功能
a2enmod rewrite

获取请求参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
namespace app\index\controller;
use think\Request;
class Index
{
public function hello(Request $request)
{
echo '请求方法:' . $request->method() . '<br/>';
echo '资源类型:' . $request->type() . '<br/>';
echo '访问IP:' . $request->ip() . '<br/>';
echo '是否AJax请求:' . var_export($request->isAjax(), true) . '<br/>';
echo '请求参数:';
dump($request->param());
echo '请求参数:仅包含name';
dump($request->only(['name']));
echo '请求参数:排除name';
dump($request->except(['name']));
}
}

获取URL信息

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
<?php
namespace app\index\controller;
use think\Request;
三、请求和响应
本文档使用 看云 构建 - 39 -
class Index
{
public function hello(Request $request,$name = 'World')
{
// 获取当前域名
echo 'domain: ' . $request->domain() . '<br/>';
// 获取当前入口文件
echo 'file: ' . $request->baseFile() . '<br/>';
// 获取当前URL地址 不含域名
echo 'url: ' . $request->url() . '<br/>';
// 获取包含域名的完整URL地址
echo 'url with domain: ' . $request->url(true) . '<br/>';
// 获取当前URL地址 不含QUERY_STRING
echo 'url without query: ' . $request->baseUrl() . '<br/>';
// 获取URL访问的ROOT地址
echo 'root:' . $request->root() . '<br/>';
// 获取URL访问的ROOT地址
echo 'root with domain: ' . $request->root(true) . '<br/>';
// 获取URL地址中的PATH_INFO信息
echo 'pathinfo: ' . $request->pathinfo() . '<br/>';
// 获取URL地址中的PATH_INFO信息 不含后缀
echo 'pathinfo: ' . $request->path() . '<br/>';
// 获取URL地址中的后缀信息
echo 'ext: ' . $request->ext() . '<br/>';
return 'Hello,' . $name . '!';
}
}


补充方法

1
2
3
4
5
$request->module()  //模块
$request->controller() //控制器
$request->action() //操作
$request->routeInfo() //路由信息
$request->dispatch() //调度信息

2)响应对象

自动输出

大多数情况,我们不需要关注 Response 对象本身,只需要在控制器的操作方法中返回数据即可,系统会根据 default_return_type 和 default_ajax_return 配置决定响应输出的类型。

默认的自动响应输出会自动判断是否 AJAX 请求,如果是的话会自动输出 default_ajax_return 配置的输出类型。

1
2
3
4
5
6
7
8
9
10
<?php
namespace app\index\controller;
class Index
{
public function hello()
{
$data = ['name' => 'thinkphp', 'status' => '1'];
return json($data); //这里需要在config设置输出格式为json
}
}

手动输出

1
2
3
4
5
6
7
8
9
10
<?php
namespace app\index\controller;
class Index
{
public function hello()
{
$data = ['name' => 'thinkphp', 'status' => '1'];
return json($data, 201, ['Cache-control' => 'no-cache,must-revalidate']);
}
}

页面重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

/*引入了一个 Jump trait,这是 PHP5.4 版本的新特性,如果你的控制器类是继承的
\think\Controller 的话,系统已经自动为你引入了 \traits\controller\Jump ,无需再次引入。*/

namespace app\index\controller;
class Index
{
use \traits\controller\Jump;
public function index($name='')
{
if ('thinkphp' == $name) {
$this->redirect('http://thinkphp.cn');
} else {
$this->success('欢迎使用ThinkPHP','hello');
}
}
public function hello()
{
return 'Hello,ThinkPHP!';
}
}

四、数据库

5.0 的数据查询由低到高分三个层次:

    1. 数据库原生查询(SQL查询);
    1. 数据库链式查询(查询构造器);
    1. 模型的对象化查询;

      1.原生查询

      设置好数据库连接信息后,我们就可以直接进行原生的SQL查询操作了,包括 query 和 execute 两个方法,分别用于查询和写入。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      // 插入记录
      $result = Db::execute('insert into think_data (id, name ,status) values (5, "thinkphp",1)');
      dump($result);

      // 更新记录
      $result = Db::execute('update think_data set name = "framework" where id = 5 ');
      dump($result);

      // 查询数据
      $result = Db::query('select * from think_data where id = 5');
      dump($result);

      // 删除数据
      $result = Db::execute('delete from think_data where id = 5 ');
      dump($result);

      // 显示数据库列表
      $result = Db::query('show tables from demo');
      dump($result);

      // 清空数据表
      $result = Db::execute('TRUNCATE table think_data');
      dump($result);

参数绑定

实际开发中,可能某些数据使用的是外部传入的变量,为了让查询操作更加安全,我们建议使用参数绑定机制,例如上面的操作可以改为:

1
2
3
Db::execute('insert into think_data (id, name ,status) values (?, ?, ?)', [8, 'thinkphp', 1]);
$result = Db::query('select * from think_data where id = ?', [8]);
dump($result);

也支持命名占位符绑定,例如:

1
2
3
Db::execute('insert into think_data (id, name , status) values (:id, :name, :status)',['id' => 10, 'name' => 'thinkphp', 'status' => 1]);
$result = Db::query('select * from think_data where id=:id', ['id' => 10]);
dump($result);

2.查询构造器

数据库查询构造器,可以更方便执行数据库操作,查询构造器基于PDO实现,对不同的数据库驱动都是统一的语法。

注意: ThinkPHP 5.0 查询构造器使用 PDO 参数绑定,以保护应用程序免于 SQL 注入,因此传入的参数不需额外转义特殊字符。

同样是实现上面的功能,我们可以改成:

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
// 插入记录
Db::table('think_data')
->insert(['id' => 18, 'name' => 'thinkphp', 'status' => 1]);

// 更新记录
Db::table('think_data')
->where('id', 18)
->update(['name' => "hello"]);

// 查询数据
$list = Db::table('think_data')
->where('id', 18)
->select();

// 删除数据
Db::table('think_data')
->where('id', 18)
->delete();

亦可使用系统提供的助手函数 db
$db = db('data');
// 插入记录
$db->insert(['id' => 20, 'name' => 'thinkphp']);

// 更新记录
$db->where('id', 20)->update(['name' => "framework"]);

// 查询数据
$list = $db->where('id', 20)->select();
dump($list);

// 删除数据
$db->where('id', 20)->delete();

/*db 助手函数默认会每次重新连接数据库,因此应当尽量避免多次调用。*/

链式操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 查询十个满足条件的数据 并按照id倒序排列
$list = Db::name('data')
->where('status', 1)
->field('id,name')
->order('id', 'desc')
->limit(10)
->select();
dump($list);

链式操作不分先后,只要在查询方法(这里是 select 方法)之前调用就行,所以,下面的查询是等效的:
// 查询十个满足条件的数据 并按照id倒序排列
$list = Db::name('data')
->field('id,name')
->order('id', 'desc')
->where('status', 1)
->limit(10)
->select();
dump($list);


事务支持

由于需要用到事务的功能,请先修改数据表的类型为 InnoDB ,而不是 MyISAM 。

对于事务的支持,最简单的方法就是使用 transaction 方法,只需要把需要执行的事务操作封装到闭包里面即可自动完成事务

1
2
3
4
5
6
Db::transaction(function () {
Db::table('think_user')
->delete(1);
Db::table('think_data')
->insert(['id' => 28, 'name' => 'thinkphp', 'status' => 1]);
});

一旦 think_data 表写入失败的话,系统会自动回滚,写入成功的话系统会自动提交当前事务。

也可以手动控制事务的提交,事务操作只对支持事务的数据库,并且设置了数据表为事务类型才有效,在Mysql数据库中请设置表类型为 InnoDB 。并且事务操作必须使用同一个数据库连接。上面的实现代码可以改成:

1
2
3
4
5
6
7
8
9
10
11
// 启动事务
Db::startTrans();
try {
Db::table('think_user')
->delete(1);
Db::table('think_data')
->insert(['id' => 28, 'name' => 'thinkphp', 'status' => 1]);
Db::commit(); // 提交事务
} catch (\Exception $e) {
Db::rollback(); // 回滚事务
}

五、模型与关联

ThinkPHP5.0 的模型是一种对象-关系映射(Object/Relation Mapping,简称 ORM )的封装,并且提供了简洁的 ActiveRecord 实现。一般来说,每个数据表会和一个“模型”对应。

ORM 的基本特性就是表映射到记录,记录映射到对象,字段映射到对象属性。模型是一种对象化的操作封装,而不是简单的 CURD 操作,简单的 CURD 操作直接使用前面提过的 Db 类即可。

模型类和 Db 类的区别主要在于对象的封装, Db 类的查询默认返回的是数组(或者集合),而模型类返回的是当前的模型对象实例(或者集合),模型是比 Db 类更高级的数据封装,支持模型关联、模型事件。

(1)模型定义

模型定义
为了更好的理解,我们首先在数据库创建一个 think_user 表如下:

1
2
3
4
5
6
7
8
9
10
CREATE TABLE IF NOT EXISTS `think_user`(
`id` int(8) unsigned NOT NULL AUTO_INCREMENT,
`nickname` varchar(50) NOT NULL COMMENT '昵称',
`email` varchar(255) NULL DEFAULT NULL COMMENT '邮箱',
`birthday` int(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '生日',
`status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '状态',
`create_time` int(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '注册时间',
`update_time` int(11) UNSIGNED NOT NULL DEFAULT '0' COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;

数据库配置文件定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
return [
// 数据库类型
'type' => 'mysql',
// 服务器地址
'hostname' => 'localhost',
// 数据库名
'database' => 'demo',
// 数据库用户名
'username' => 'root',
// 数据库密码
'password' => 'xxxxxx',
// 数据库连接端口
'hostport' => '3306',
// 数据库连接参数
'params' => [],
// 数据库编码默认采用utf8
'charset' => 'utf8',
// 数据库表前缀
'prefix' => 'think_',
// 数据库调试模式
'debug' => true,
];

并添加路由定义( application/route.php )如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
return [
// 全局变量规则定义
'__pattern__' => [
'id' => '\d+',
],
'user/index' => 'index/user/index',
'user/create' => 'index/user/create',
'user/add' => 'index/user/add',
'user/add_list' => 'index/user/addList',
'user/update/:id' => 'index/user/update',
'user/delete/:id' => 'index/user/delete',
'user/:id' => 'index/user/read',
];

我们为 think_user 表定义一个 User 模型(位于 application/index/model/User.php )如下:

1
2
3
4
5
6
7
namespace app\index\model;
use think\Model;
class User extends Model
{
// 设置完整的数据表(包含前缀)
protected $table = 'think_user';
}

大多情况下,我们无需为模型定义任何的属性和方法即可完成基础的操作。

(2)基础操作

在这之前,会遇到连接不上服务器的坑点,解决坑点
参考:https://www.jianshu.com/p/2b63c65caf6a

新增数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
namespace app\index\controller;
use app\index\model\User as UserModel;
class User
{
// 新增用户数据
public function add()
{
$user = new UserModel;
$user->nickname = '流年';
$user->email = 'thinkphp@qq.com';
$user->birthday = strtotime('1977-03-05');
if ($user->save()) {
return '用户[ ' . $user->nickname . ':' . $user->id . ' ]新增成功';
} else {
return $user->getError();
}
}
}

批量新增

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 批量新增用户数据
public function addList()
{
$user = new UserModel;
$list = [
['nickname' => '张三', 'email' => 'zhanghsan@qq.com', 'birthday' => strtotime('1
988-01-15')],
['nickname' => '李四', 'email' => 'lisi@qq.com', 'birthday' => strtotime('1990-0
9-19')],
];
if ($user->saveAll($list)) {
return '用户批量新增成功';
} else {
return $user->getError();
}
}

查询数据

接下来添加 User 模型的查询功能,给 User 控制器增加如下 read 操作方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 读取用户数据
public function read($id='')
{
//$user = new UserModel;
//$user = $user::get($id);

$user = UserModel::get($id);
echo $user->nickname . '<br/>';
echo $user->email . '<br/>';
echo date('Y/m/d', $user->birthday) . '<br/>';

echo $user['nickname'] . '<br/>';
echo $user['email'] . '<br/>';
echo date('Y/m/d', $user['birthday']) . '<br/>';
}

模型的 get 方法和 Db 类的 find 方法返回结果的区别在于, Db 类默认返回的只是数组(注意这里说的默认,其实仍然可以设置为对象),而模型的 get 方法查询返回的一定是当前的模型对象实例。但是系统为模型实现了 ArrayAccess 接口,因此仍然可以通过数组的方式访问对象实例

如果我想通过用户的 email 来查询模型数据的话,应该如何操作呢?

1
2
3
4
5
6
7
8
// 根据email读取用户数据
public function read()
{
$user = UserModel::getByEmail('thinkphp@qq.com');
echo $user->nickname . '<br/>';
echo $user->email . '<br/>';
echo date('Y/m/d', $user->birthday) . '<br/>';
}

如果不是根据主键查询的话,可以传入数组作为查询条件,例如:

1
2
3
4
5
6
7
8
// 根据nickname读取用户数据
public function read()
{
$user = UserModel::get(['nickname'=>'流年']);
echo $user->nickname . '<br/>';
echo $user->email . '<br/>';
echo date('Y/m/d', $user->birthday) . '<br/>';
}

更复杂的查询则可以使用查询构建器来完成,例如:

1
2
3
4
5
6
7
8
// 根据nickname读取用户数据
public function read()
{
$user = UserModel::where('nickname', '流年')->find();
echo $user->nickname . '<br/>';
echo $user->email . '<br/>';
echo date('Y/m/d', $user->birthday) . '<br/>';
}

数据列表

如果要查询多个数据,可以使用模型的 all 方法,我们在控制器中添加index操作方法用于获取用户列表:

1
2
3
4
5
6
7
8
9
10
11
 // 获取用户数据列表
public function index()
{
$list = UserModel::all();
foreach ($list as $user) {
echo $user->nickname . '<br/>';
echo $user->email . '<br/>';
echo date('Y/m/d', $user->birthday) . '<br/>';
echo '----------------------------------<br/>';
}
}

如果不是使用主键查询,可以直接传入数组条件查询,例如:

1
2
3
4
5
6
7
8
9
10
11
// 获取用户数据列表
public function index()
{
$list = UserModel::all(['status'=>1]);
foreach ($list as $user) {
echo $user->nickname . '<br/>';
echo $user->email . '<br/>';
echo date('Y/m/d', $user->birthday) . '<br/>';
echo '----------------------------------<br/>';
}
}

同样,复杂的用查询构建器

1
2
3
4
5
6
7
8
9
10
11
// 获取用户数据列表
public function index()
{
$list = UserModel::where('id','<',3)->select();
foreach ($list as $user) {
echo $user->nickname . '<br/>';
echo $user->email . '<br/>';
echo date('Y/m/d', $user->birthday) . '<br/>';
echo '----------------------------------<br/>';
}
}

更新数据

我们可以对查询出来的数据进行更新操作,下面添加一个 update 操作方法:

1
2
3
4
5
6
7
8
9
10
11
12
// 更新用户数据
public function update($id)
{
$user = UserModel::get($id);
$user->nickname = '刘晨';
$user->email = 'liu21st@gmail.com';
if (false !== $user->save()) {
return '更新用户成功';
} else {
return $user->getError();
}
}

默认情况下,查询模型数据后返回的模型示例执行 save 操作都是执行的数据库 update 操作,如果你需要实例化执行 save 执行数据库的 insert 操作,请确保在save方法之前调用 isUpdate 方法:

1
$user->isUpdate(false)->save();

ActiveRecord 模式的更新数据方式需要首先读取对应的数据,如果需要更高效的方法可以把update方法
改成:

1
2
3
4
5
6
7
8
9
// 更新用户数据
public function update($id)
{
$user['id'] = (int) $id;
$user['nickname'] = '刘晨';
$user['email'] = 'liu21st@gmail.com';
$result = UserModel::update($user);
return '更新用户成功';
}

删除数据

我们给User控制器添加delete方法用于删除用户。

1
2
3
4
5
6
7
8
9
10
11
// 删除用户数据
public function delete($id)
{
$user = UserModel::get($id);
if ($user) {
$user->delete();
return '删除用户成功';
} else {
return '删除的用户不存在';
}
}

同样我们也可以直接使用 destroy 方法删除模型数据,例如把上面的 delete 方法改成如下:

1
2
3
4
5
6
7
8
9
10
// 删除用户数据
public function delete($id)
{
$result = UserModel::destroy($id);
if ($result) {
return '删除用户成功';
} else {
return '删除的用户不存在';
}
}

同样我们也可以直接使用 destroy 方法删除模型数据,例如把上面的 delete 方法改成如下:

1
2
3
4
5
6
7
8
9
10
// 删除用户数据
public function delete($id)
{
$result = UserModel::destroy($id);
if ($result) {
return '删除用户成功';
} else {
return '删除的用户不存在';
}
}

(3)读取器和修改器

获取器

获取器的作用是在获取数据的字段值后自动进行处理,例如,我们需要对状态值进行转换,可以使用:

读取器方法的命名规范是:get + 属性名的驼峰命名+ Attr

1
2
3
4
5
6
7
8
9
10
11
12
13
class User extends Model 
{
public function getStatusAttr($value)
{
$status = [-1=>'删除',0=>'禁用',1=>'正常',2=>'待审核'];
return $status[$value];
}
}

数据表的字段会自动转换为驼峰法,一般status字段的值采用数值类型,我们可以通过获取器定义,自动转换为字符串描述。

$user = User::get(1);
echo $user->status; // 例如输出“正常”

获取器还可以定义数据表中不存在的字段,例如:

1
2
3
4
5
6
7
8
9
10
11
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// user_birthday读取器
protected function getUserBirthdayAttr($value,$data)
{
return date('Y-m-d', $data['birthday']);
}
}

这里的读取器方法使用了第二个参数,表示传入所有的属性数据。因为原始的 user_birthday 属性数据是
不存在的,所以我们需要通过 data 参数获取。

1
2
3
4
5
6
7
8
9
// 读取用户数据
public function read($id='')
{
$user = UserModel::get($id);
echo $user->nickname . '<br/>'; //流年
echo $user->email . '<br/>'; //thinkphp@qq.com
echo $user->birthday . '<br/>'; //226339200
echo $user->user_birthday . '<br/>'; //1977-03-05
}

如果你定义了获取器的情况下,希望获取数据表中的原始数据,可以使用:

1
2
3
4
5
6
7
$user = User::get(1);
// 通过获取器获取字段
echo $user->status;
// 获取原始字段数据
echo $user->getData('status'); //1
// 获取全部原始数据
dump($user->getData());

修改器

修改器方法的命名规范是:set + 属性名的驼峰命名+ Attr

由于 birthday 属性是时间戳(整型)格式的,因此我们必须在写入数据前进行时间戳转换,前面使用的方法是每次赋值的时候进行转换处理:

1
$user['birthday'] = strtotime('2015-04-02');

为了避免每次都进行日期格式的转换操作,可以定义修改器方法来自动处理,修改 User 模型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 读取器
protected function getUserBirthdayAttr($birthday, $data)
{
return date('Y-m-d', $data['birthday']);
}
// birthday修改器
protected function setBirthdayAttr($value)
{
return strtotime($value);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 新增用户数据
public function add()
{
$user = new UserModel;
$user->nickname = '流年';
$user->email = 'thinkphp@qq.com';
$user->birthday = '1977-03-05';
if ($user->save()) {
return '用户[ ' . $user->nickname . ':' . $user->id . ' ]新增成功'; //用户[ 流年:10 ]新增成功
} else {
return $user->getError();
}
}

通过定义修改器和读取器,完成了时间戳方式存储的 birthday 属性的写入和读取的自动处理。

(4)类型转换和自动完成

类型转换

对于前面的时间戳 birthday 的例子,还可以进行进一步的简化,这里需要用到类型强制转换的功能,在User 模型类中添加定义:

1
2
3
4
5
6
7
8
9
10
11
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $dateFormat = 'Y/m/d';
protected $type = [
// 设置birthday为时间戳类型(整型)
'birthday' => 'timestamp',
];
}

不需要定义任何修改器和读取器,我们完成了相同的功能。
对于 timestamp 和 datetime 类型,如果不设置模型的 dateFormat 属性,默认的日期显示格式为:Y-m-d H:i:s ,或者也可以显示的设置日期格式,例如:

1
2
3
4
5
6
7
8
9
10
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $type = [
// 设置birthday为时间戳类型(整型)
'birthday' => 'timestamp:Y/m/d',
];
}

自动时间戳

对于固定的时间戳和时间日期型的字段,比如文章的创建时间、修改时间等字段,还有比设置类型转换更简单的方法,尤其是所有的数据表统一处理的话,只需要在数据库配置文件中添加设置:

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
// 开启自动写入时间戳字段
'auto_timestamp' => true,

访问http://tp5.com/user/add

会发现系统已经自动写入了 think_user 数据表中的的 create_time 、 update_time 字段,如果自动
写入的时间戳字段不是这两个的话,需要修改模型类的属性定义,例如:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 定义类型转换
protected $type = [
'birthday' => 'timestamp:Y/m/d',
];
// 定义时间戳字段名
protected $createTime = 'create_at';
protected $updateTime = 'update_at';
}

如果个别数据表不需要自动写入时间戳字段的话,也可以在模型里面直接关闭:
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $type = [
'birthday' => 'timestamp:Y/m/d',
];
// 关闭自动写入时间戳
protected $autoWriteTimestamp = false;
}

自动完成

系统已经自动写入了 think_user 数据表中的的 create_time 、 update_time 字段,如果我们希望自动写入其它的字段,则可以使用自动完成功能,例如下面实现新增的时候自动写入 status 字段。

1
2
3
4
5
6
7
8
9
10
11
12
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 定义类型转换
protected $type = [
'birthday' => 'timestamp:Y/m/d',
];
// 定义自动完成的属性
protected $insert = ['status' => 1];
}

image.png
自动完成属性里面一般来说仅仅需要定义属性的名称,然后配合修改器或者类型转换来一起完成,如果写入的是一个固定的值,就无需使用修改器。 status 属性的自动写入可以直接使用:

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
'status' => 1
完成后,我们访问URL地址:
http://localhost/user/add

最后的输出结果为:
用户[ 流年:12 ]新增成功

为了便于看到效果,我们修改控制器的 read 操作方法输出更多的属性:
// 读取用户数据
public function read($id='')
{
$user = UserModel::get($id);
echo $user->nickname . '<br/>';
echo $user->email . '<br/>';
echo $user->birthday . '<br/>';
echo $user->status . '<br/>';
echo $user->create_time . '<br/>';
echo $user->update_time . '<br/>';
}
然后,访问URL地址:http://localhost/user/12

最后的输出结果为:
流年
thinkphp@qq.com
1977/03/05
1
2016-05-02 16:21:33
2016-05-02 16:21:33

可以看到 status 、 create_time 和 update_time 都实现了自动写入。

如果你的 status 属性的值不是固定的,而是需要条件判断,那么我们可以定义修改器来配合自动完成。

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
37
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 定义类型转换
protected $type = [
'birthday' => 'timestamp:Y/m/d',
];
// 定义自动完成的属性
protected $insert = ['status'];
// status属性修改器
protected function setStatusAttr($value, $data)
{
return '流年' == $data['nickname'] ? 1 : 2;
}
// status属性读取器
protected function getStatusAttr($value)
{
$status = [-1 => '删除', 0 => '禁用', 1 => '正常', 2 => '待审核'];
return $status[$value];
}
}

我们访问下面的URL地址进行批量新增
http://localhost/user/add_list

之后,访问
http://localhost/user/18

最后的输出结果为:
张三
zhanghsan@qq.com
1988/01/15
待审核
2016-05-02 16:40:57
2016-05-02 16:40:57

(5)查询范围

查询范围方法的定义规范为:scope + 查询范围名称

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
例如,邮箱地址为 thinkphp@qq.com 和status为1这两个常用查询条件,可以定义为模型类的两个查询范围方法:

<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 定义类型转换
protected $type = [
'birthday' => 'timestamp:Y/m/d',
];
// 定义自动完成的属性
protected $insert = ['status'];
// status修改器
protected function setStatusAttr($value, $data)
{
return '流年' == $data['nickname'] ? 1 : 2;
}
// status读取器
protected function getStatusAttr($value)
{
$status = [-1 => '删除', 0 => '禁用', 1 => '正常', 2 => '待审核'];
return $status[$value];
}
// email查询
protected function scopeEmail($query)
{
$query->where('email', 'thinkphp@qq.com');
}
// status查询
protected function scopeStatus($query)
{
$query->where('status', 1);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 根据查询范围获取用户数据列表
public function index()
{
$list = UserModel::scope('email,status')->all();
foreach ($list as $user) {
echo $user->nickname . '<br/>';
echo $user->email . '<br/>';
echo $user->birthday . '<br/>';
echo $user->status . '<br/>';
echo '-------------------------------------<br/>';
}
}

最后查询的SQL语句是:
SELECT * FROM `think_user` WHERE `email` = 'thinkphp@qq.com' AND `status` = 1

支持多次调用 scope 方法,并且可以追加新的查询及链式操作,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 根据查询范围获取用户数据列表
public function index()
{
$list = UserModel::scope('email')
->scope('status')
->scope(function ($query) {
$query->order('id', 'desc');
})
->all();
foreach ($list as $user) {
echo $user->nickname . '<br/>';
echo $user->email . '<br/>';
echo $user->birthday . '<br/>';
echo $user->status . '<br/>';
echo '-------------------------------------<br/>';
}
}

最后生成的SQL语句是:
SELECT * FROM `think_user` WHERE `email` = 'thinkphp@qq.com' AND `status` = 1 ORDER BY `id` desc

查询范围方法支持额外的参数,例如 scopeEmail 方法改为:

1
2
3
4
5
6
7
8
9
10
// email查询
protected function scopeEmail($query, $email = '')
{
$query->where('email', $email);
}
查询范围的方法的第一个参数必须是查询对象,并且支持多个额外参数。

然后,使用下面的方式调用即可:

$list = UserModel::scope('email','thinkphp@qq.com')->all();

全局查询范围

可以给模型定义全局的查询范围,在模型类添加一个静态的 base 方法即可,例如我们给模型类增加一个全局查询范围,用于查询状态为1的数据:

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
37
38
39
40
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 定义类型转换
protected $type = [
'birthday' => 'timestamp:Y/m/d',
];
// 定义自动完成的属性
protected $insert = ['status'];
// status修改器
protected function setStatusAttr($value, $data)
{
return '流年' == $data['nickname'] ? 1 : 2;
}
// status读取器
protected function getStatusAttr($value)
{
$status = [-1 => '删除', 0 => '禁用', 1 => '正常', 2 => '待审核'];
return $status[$value];
}
// email查询
protected function scopeEmail($query)
{
$query->where('email', 'thinkphp@qq.com');
}
// 全局查询范围
protected static function base($query)
{
// 查询状态为1的数据
$query->where('status',1);
}
}

当使用下面的查询操作
UserModel::scope('email')->all();
最后生成的SQL语句是:
SELECT * FROM `think_user` WHERE `email` = 'thinkphp@qq.com' AND `status` = 1 ORDER BY `id` desc
每次查询都会自动带上全局查询范围的查询条件。

(6)输入和验证

现在我们来进一步使用表单提交数据完成模型的对象操作,主要内容包含:

  • 表单提交
  • 表单验证
  • 错误提示
  • 自定义验证规则
  • 控制器验证

表单提交
首先创建一个视图模板文件 application/index/view/user/create.html ,内容如下:、

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>创建用户</title>
<style>
body {
font-family:"Microsoft Yahei","Helvetica Neue",Helvetica,Arial,sans-serif;
font-size:16px;
padding:5px;
}
.form{
padding: 15px;
font-size: 16px;
}
.form .text {
padding: 3px;
margin:2px 10px;
width: 240px;
height: 24px;
line-height: 28px;
border: 1px solid #D4D4D4;
}
.form .btn{
margin:6px;
padding: 6px;
width: 120px;
font-size: 16px;
border: 1px solid #D4D4D4;
cursor: pointer;
background:#eee;
}
a{
color: #868686;
cursor: pointer;
}
a:hover{
text-decoration: underline;
}
h2{
color: #4288ce;
font-weight: 400;
padding: 6px 0;
margin: 6px 0 0;
font-size: 28px;
border-bottom: 1px solid #eee;
}
div{
margin:8px;
}
.info{
padding: 12px 0;
border-bottom: 1px solid #eee;
}
.copyright{
margin-top: 24px;
padding: 12px 0;
border-top: 1px solid #eee;
}
</style>
</head>
<body>
<h2>创建用户</h2>
<FORM method="post" class="form" action="{:url('index/user/add')}">
昵 称:<INPUT type="text" class="text" name="nickname"><br/>
邮 箱:<INPUT type="text" class="text" name="email"><br/>
生 日:<INPUT type="text" class="text" name="birthday"><br/>
<input type="hidden" name="__token__" value="{$Request.token}" />
<INPUT type="submit" class="btn" value=" 提交 ">
</FORM>
<div class="copyright">
<a title="官方网站" href="http://www.thinkphp.cn">ThinkPHP</a>
<span>V5</span>
<span>{ 十年磨一剑-为API开发设计的高性能框架 }</span>
</div>
</body>
</html>

User控制器增加新的操作方法 create 如下:
view 方法是系统封装的助手函数用于快速渲染模板文件,这里没有传入模板文件,则按照系统默认的解析规则会自动渲染当前操作方法对应的模板文件,也就是默认视图目录(application/index/view)下面的 user/create.html 文件,所以如果改成下面的方式是相同的:

1
2
3
4
5
// 创建用户数据页面
public function create()
{
return view('user/create');
}

并且修改之前的 add 方法如下:

1
2
3
4
5
6
7
8
9
10
// 新增用户数据
public function add()
{
$user = new UserModel;
if ($user->allowField(true)->save(input('post.'))) {
return '用户[ ' . $user->nickname . ':' . $user->id . ' ]新增成功';
} else {
return $user->getError();
}
}

表单验证

作为安全狗,永远不要相信用户的数据,所以现在给表单提交添加数据验证。
我们添加一个 User 验证器(位于 application/index/validate/User.php ),代码如下:

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
37
38
39
40
41
42
43
44
45
<?php
namespace app\index\validate;
use think\Validate;
class User extends Validate
{
// 验证规则
protected $rule = [
'nickname' => 'require|min:5|token',
'email' => 'require|email',
'birthday' => 'dateFormat:Y-m-d',
];

//防混淆,可以用数组形式
/*protected $rule = [
'nickname' => ['require', 'min'=>5, 'token'],
'email' => ['require', 'email'],
'birthday' => ['dateFormat' => 'Y|m|d'],
];*/

//控制回显
/*protected $rule = [
['nickname', 'require|min:5', '昵称必须|昵称不能短于5个字符'],
['email', 'email', '邮箱格式错误'],
['birthday', 'dateFormat:Y-m-d', '生日格式错误'],
];*/
}
User 验证器添加了三个属性的验证规则,分别表示:
昵称必须,而且最小长度为5
邮箱必须,而且必须是合法的邮件地址
生日可选,如果填写的话必须为 Y-m-d 格式的日期格式

除非使用了 require 开头的规则,否则所有的验证都是可选的(也就是说有值才验证)多个验证之间用 | 分割,并且按照先后顺序依次进行验证,一旦某个规则验证失败,后续的规则就不会再进行验证(除非设置批量验证方式则统一返回所有的错误信息)。

然后对控制器的 add 方法则稍加修改,在 save 方法之前添加一个 validate 方法即可:
// 新增用户数据
public function add()
{
$user = new UserModel;
if ($user->allowField(true)->validate(true)->save(input('post.'))) {
return '用户[ ' . $user->nickname . ':' . $user->id . ' ]新增成功';
} else {
return $user->getError();
}
}
系统提供了丰富的内置验证规则,具体可以参考完全开发手册

自定义验证规则

系统的验证规则(可以)满足大部分的验证场景,但有时候我们也需要自定义特殊的验证规则,例如我们需要验证邮箱必须为 thinkphp.cn 域名的话,可以在 User 验证器中添加验证规则如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
namespace app\index\validate;
use think\Validate;
class User extends Validate
{
// 验证规则
protected $rule = [
['nickname', 'require|min:5', '昵称必须|昵称不能短于5个字符'],
['email', 'checkMail:thinkphp.cn', '邮箱格式错误'],
['birthday', 'dateFormat:Y-m-d', '生日格式错误'],
];
// 验证邮箱格式 是否符合指定的域名
protected function checkMail($value, $rule)
{
$result = preg_match('/^\w+([-+.]\w+)*@' . $rule . '$/', $value);
if (!$result) {
return '邮箱只能是' . $rule . '域名';
} else {
return true;
}
}
}

控制器验证

前面我们讲了在模型中使用验证器进行数据验证的方法,下面来讲下如何在控制器中进行数据验证。
验证器类的定义不变,现在修改下控制器类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace app\index\controller;
use app\index\model\User as UserModel;
use think\Controller;
class User extends Controller
{
// 创建用户数据页面
public function create()
{
return view();
}
public function add()
{
$data = input('post.');
// 数据验证
$result = $this->validate($data,'User');
if (true !== $result) {
return $result;
}
$user = new UserModel;
// 数据保存
$user->allowField(true)->save($data);
return '用户[ ' . $user->nickname . ':' . $user->id . ' ]新增成功';
}
}

如果有一些个别的验证没有在验证器里面定义,也可以使用静态方法单独处理,例如下面对birthday字段单独验证是否是一个有效的日期格式:

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
namespace app\index\controller;
use app\index\model\User as UserModel;
use think\Controller;
use think\Validate;
class User extends Controller
{
// 创建用户数据页面
public function create()
{
return view();
}
public function add()
{
$data = input('post.');
// 验证birthday是否有效的日期
$check = Validate::is($data['birthday'],'date');
if (false === $check) {
return 'birthday日期格式非法';
}
$user = new UserModel;
// 数据保存
$user->save($data);
return '用户[ ' . $user->nickname . ':' . $user->id . ' ]新增成功';
}
}

(7)关联

  • 基本定义
    • 一对一关联
      • 关联定义
      • 关联写入
      • 关联查询
      • 关联更新
      • 关联删除
      • 一对多关联
        * 关联定义
        * 关联新增
        * 关联查询
        * 关联更新
        * 关联删除
  • 多对多关联
    * 关联定义
    * 关联新增
    * 关联删除
    * 关联查询 

    基本定义

    ThinkPHP5.0 的关联采用了对象化的操作模式,你无需继承不同的模型类,只是把关联定义成一个方法,并且直接通过当前模型对象的属性名获取定义的关联数据。
    1
    2
    3
    4
    一般来说,关联关系包括:
    一对一关联: HAS_ONE 以及相对的 BELONGS_TO
    一对多关联: HAS_MANY 以及相对的 BELONGS_TO
    多对多关联: BELONGS_TO_MANY

一对一关联

一对一关联是一种最简单的关联,例如每个用户都有一份档案,每个公司都有一个营业执照等等。
在这之前,我们先创建数据表如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
DROP TABLE IF EXISTS `think_user`;
CREATE TABLE IF NOT EXISTS `think_user` (
`id` int(6) UNSIGNED NOT NULL AUTO_INCREMENT,
`nickname` varchar(25) NOT NULL,
`name` varchar(25) NOT NULL,
`password` varchar(50) NOT NULL,
`create_time` int(11) UNSIGNED NOT NULL,
`update_time` int(11) UNSIGNED NOT NULL,
`status` tinyint(1) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS `think_profile`;
CREATE TABLE IF NOT EXISTS `think_profile` (
`id` int(6) UNSIGNED NOT NULL AUTO_INCREMENT,
`truename` varchar(25) NOT NULL,
`birthday` int(11) NOT NULL,
`address` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`user_id` int(6) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

关联定义

我们以用户和档案的一对一关联为例,在 User 模型类中添加关联定义方法,然后在方法中调用 hasOne 方法即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 开启自动写入时间戳
protected $autoWriteTimestamp = true;
// 定义自动完成的属性
protected $insert = ['status' => 1];
// 定义关联方法
public function profile()
{
// 用户HAS ONE档案关联
return $this->hasOne('Profile');
}
}

hasOne方法有5个参数,依次分别是:
hasOne(‘关联模型名’,’关联外键’,’主键’,’别名定义’,’join类型’)

默认的外键是:当前模型名_id,主键则是自动获取,如果你的表设计符合这一规范的话,只需要设置关联的模型名即可
要进行模型的关联操作,我们必须同时定义好关联模型, Profile 模型定义如下:

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
<?php
namespace app\index\model;
use think\Model;
class Profile extends Model
{
protected $type = [
'birthday' => 'timestamp:Y-m-d',
];
}

可以看到 Profile 模型中并没有定义关联方法。如果你的关联操作都是基于 User 模型的话, Profile模型中并不需要定义关联方法。
如果你需要基于 Profile 模型来进行关联操作,则需要在 Profile 模型中定义对应的 BELONGS_TO 关联,如下:
<?php
namespace app\index\model;
use think\Model;
class Profile extends Model
{
protected $type = [
'birthday' => 'timestamp:Y-m-d',
];
public function user()
{
// 档案 BELONGS TO 关联用户
return $this->belongsTo('User');
}
}

belongsTo 方法和 hasOne 一样,也有5个参数 ,依次分别是:
belongsTo(‘关联模型名’,’关联外键’,’关联模型主键’,’别名定义’,’join类型’)

关联写入

首先来看下如何进行关联数据的写入,创建User控制器的add操作方法如下:

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 app\index\controller;
use app\index\model\Profile;
use app\index\model\User as UserModel;
class User
{
// 关联新增数据
public function add()
{
$user = new UserModel;
$user->name = 'thinkphp';
$user->password = '123456';
$user->nickname = '流年';
if ($user->save()) {
// 写入关联数据
$profile = new Profile;
$profile->truename = '刘晨';
$profile->birthday = '1977-03-05';
$profile->address = '中国上海';
$profile->email = 'thinkphp@qq.com';
$user->profile()->save($profile);
return '用户新增成功';

/*$profile['truename'] = '刘晨';
$profile['birthday'] = '1977-03-05';
$profile['address'] = '中国上海';
$profile['email'] = 'thinkphp@qq.com';
$user->profile()->save($profile);
return '用户[ ' . $user->name . ' ]新增成功';*/
} else {
return $user->getError();
}
}
}
关联模型的写入调用了关联方法 profile() ,该方法返回的是一个 Relation 对象,执行 save 方法会自动传入当前模型 User 的主键作为关联键值,所以不需要手动传入 Profile 模型的 user_id 属性。

关联查询

一对一的关联查询很简单,直接把关联对象当成属性来用即可,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public function read($id)
{
$user = UserModel::get($id);
echo $user->name . '<br/>';
echo $user->nickname . '<br/>';
echo $user->profile->truename . '<br/>';
echo $user->profile->email . '<br/>';

//使用预载入查询来提高查询性能,避免多次查询。
/*$user = UserModel::get($id,'profile');
echo $user->name . '<br/>';
echo $user->nickname . '<br/>';
echo $user->profile->truename . '<br/>';
echo $user->profile->email . '<br/>';*/
//get方法使用第二个参数就表示进行关联预载入查询。
}

关联更新

一对一的关联更新如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public function update($id)
{
$user = UserModel::get($id);
$user->name = 'framework';
if ($user->save()) {
// 更新关联数据
$user->profile->email = 'liu21st@gmail.com';
$user->profile->save();
return '用户[ ' . $user->name . ' ]更新成功';
} else {
return $user->getError();
}
}

关联删除

关联删除代码如下:

1
2
3
4
5
6
7
8
9
10
11
public function delete($id)
{
$user = UserModel::get($id);
if ($user->delete()) {
// 删除关联数据
$user->profile->delete();
return '用户[ ' . $user->name . ' ]删除成功';
} else {
return $user->getError();
}
}

一对多关联

每个作者写有多本书就是一个典型的一对多关联,首先创建如下数据表:

1
2
3
4
5
6
7
8
9
10
11
DROP TABLE IF EXISTS `think_book`;
CREATE TABLE IF NOT EXISTS `think_book` (
`id` int(8) UNSIGNED NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`publish_time` int(11) UNSIGNED DEFAULT NULL,
`create_time` int(11) UNSIGNED NOT NULL,
`update_time` int(11) UNSIGNED NOT NULL,
`status` tinyint(1) NOT NULL,
`user_id` int(6) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

关联定义

在 User 模型类添加 Book 关联如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 开启自动写入时间戳
protected $autoWriteTimestamp = true;
// 定义自动完成的属性
protected $insert = ['status' => 1];
// 定义关联方法
public function profile()
{
return $this->hasOne('Profile');
}
// 定义关联
public function books()
{
return $this->hasMany('Book');
}
}

hasMany 的参数如下:hasMany(‘关联模型名’,’关联外键’,’关联模型主键’,’别名定义’)

Book 模型类定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  <?php
namespace app\index\model;
use think\Model;
class Book extends Model
{
protected $type = [
'publish_time' => 'timestamp:Y-m-d',
];
// 开启自动写入时间戳
protected $autoWriteTimestamp = true;
// 定义自动完成的属性
protected $insert = ['status' => 1];
// 定义关联方法
public function user()
{
return $this->belongsTo('User');
}
}

关联新增

添加 addBook 方法用于新增关联数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function addBook()
{
$user = UserModel::get(1);
$book = new Book;
$book->title = 'ThinkPHP5快速入门';
$book->publish_time = '2016-05-06';
$user->books()->save($book);
return '添加Book成功';

//批量新增
/*$user = UserModel::get(1);
$books = [
['title' => 'ThinkPHP5快速入门', 'publish_time' => '2016-05-06'],
['title' => 'ThinkPHP5开发手册', 'publish_time' => '2016-03-06'],
];
$user->books()->saveAll($books);
return '添加Book成功'; */
}

关联更新

1
2
3
4
5
6
7
8
9
10
11
12
13
public function update($id)
{
$user = UserModel::get($id);
$book = $user->books()->getByTitle('ThinkPHP5开发手册');
$book->title = 'ThinkPHP5快速入门';
$book->save();
}
或者使用查询构建器的 update 方法进行更新(但可能无法触发关联模型的事件)。
public function update($id)
{
$user = UserModel::get($id);
$user->books()->where('title', 'ThinkPHP5快速入门')->update(['title' => 'ThinkPHP5开发手册']);
}

关联删除

删除部分关联数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function delete($id){
$user = UserModel::get($id);
// 删除部分关联数据
$book = $user->books()->getByTitle('ThinkPHP5开发手册');
$book->delete();
}
删除所有的关联数据:

public function delete($id){
$user = UserModel::get($id);
if($user->delete()){
// 删除所有的关联数据
$user->books()->delete();
}
}

多对多关联

一个用户会有多个角色,同时一个角色也会包含多个用户,这就是一个典型的多对多关联,先创建一个角色表 think_role 结构如下:

1
2
3
4
5
6
7
DROP TABLE IF EXISTS `think_role`;
CREATE TABLE IF NOT EXISTS `think_role` (
`id` int(5) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` varchar(25) NOT NULL,
`title` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

多对多关联通常一定会有一个中间表,也称为枢纽表,所以需要创建一个用户角色的中间表,这里创建了一个 think_access 表,结构如下:

1
2
3
4
5
DROP TABLE IF EXISTS `think_access`;
CREATE TABLE IF NOT EXISTS `think_access` (
`user_id` int(6) UNSIGNED NOT NULL,
`role_id` int(5) UNSIGNED NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

关联定义

给User模型添加多对多关联方法定义

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
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 开启自动写入时间戳
protected $autoWriteTimestamp = true;
// 定义自动完成的属性
protected $insert = ['status' => 1];
// 定义一对一关联
public function profile()
{
return $this->hasOne('Profile');
}
// 定义一对多关联
public function books()
{
return $this->hasMany('Book');
}
// 定义多对多关联
public function roles()
{
// 用户 BELONGS_TO_MANY 角色
return $this->belongsToMany('Role', 'think_access');
}
}

belongsToMany 的参数如下:
belongsToMany(‘关联模型名’,’中间表名称’,’关联外键’,’关联模型主键’,’别名定义’)

Role 模型定义如下:

1
2
3
4
5
6
7
8
9
10
11
<?php
namespace app\index\model;
use think\Model;
class Role extends Model
{
public function user()
{
// 角色 BELONGS_TO_MANY 用户
return $this->belongsToMany('User', 'think_access');
}
}

对于枢纽表并不需要创建模型类,在多对多关联关系中,并不需要直接操作枢纽表。

关联新增

给某个用户增加编辑角色,并且由于这个角色还没创建过,所以可以使用下面的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public function add()
{
$user = UserModel::getByNickname('张三');
// 新增用户角色 并自动写入枢纽表
$user->roles()->save(['name' => 'editor', 'title' => '编辑']);
return '用户角色新增成功';
}

//也可以批量新增用户的角色如下:

public function add()
{
$user = UserModel::getByNickname('张三');
// 给当前用户新增多个用户角色
$user->roles()->saveAll([
['name' => 'leader', 'title' => '领导'],
['name' => 'admin', 'title' => '管理员'],
]);
return '用户角色新增成功';
}

现在给另外一个用户增加编辑角色,由于该角色已经存在了,所以只需要使用 attach 方法增加枢纽表的关联数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 关联新增数据
public function add()
{
$user = UserModel::getByNickname('张三');
$role = Role::getByName('editor');
// 添加枢纽表数据
$user->roles()->attach($role);
return '用户角色添加成功';
}

或者直接使用角色Id添加关联数据
// 关联新增数据
public function add()
{
$user = UserModel::getByNickname('张三');
$user->roles()->attach(1);
return '用户角色添加成功';
}

关联删除

如果需要解除用户的管理角色,可以使用 detach 方法删除关联的枢纽表数据,但不会删除关联模型数据,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 关联删除数据
public function delete()
{
$user = UserModel::get(2);
$role = Role::getByName('admin');
// 删除关联数据 但不删除关联模型数据
$user->roles()->detach($role);
return '用户角色删除成功';
}

如果有必要,也可以删除枢纽表的同时删除关联模型,下面的例子会解除用户的编辑角色并且同时删除编辑这个角色身份:
// 关联删除数据
public function delete()
{
$user = UserModel::getByNickname('张三');
$role = Role::getByName('editor');
// 删除关联数据 并同时删除关联模型数据
$user->roles()->detach($role,true);
return '用户角色删除成功';
}

关联查询

获取用户张三的所有角色的话,直接使用:

1
2
3
4
5
6
7
8
9
10
11
// 关联查询
public function read()
{
$user = UserModel::getByNickname('张三');
dump($user->roles);


// 预载入查询
$user = UserModel::get(2,'roles');
dump($user->roles);
}

(8)模型输出

以 User 模型为例,模型定义如下:

1
2
3
4
5
6
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
}

可以输出模型实例对象为数组或者 JSON 。

  • 输出数组
  • 隐藏属性
  • 指定属性
  • 追加属性
  • 输出JSON

    输出数组

    可以使用 toArray 方法把当前的模型对象输出为数组。
    1
    2
    3
    4
    5
    6
    7
    修改 User 控制器的 read 操作方法如下:
    // 读取用户数据并输出数组
    public function read($id = '')
    {
    $user = UserModel::get($id);
    dump($user->toArray());
    }

隐藏属性

1
2
3
4
5
6
// 读取用户数据并输出数组
public function read($id = '')
{
$user = UserModel::get($id);
dump($user->hidden(['create_time','update_time'])->toArray());
}

指定属性

1
2
3
4
5
6
// 读取用户数据并输出数组
public function read($id = '')
{
$user = UserModel::get($id);
dump($user->visible(['id','nickname','email'])->toArray());
}

追加属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// status修改器
protected function getUserStatusAttr($value)
{
$status = [-1 => '删除', 0 => '禁用', 1 => '正常', 2 => '待审核'];
return $status[$value];
}
}

而我们如果需要输出 user_status 属性数据的话,可以使用 append 方法,用法如下:
// 读取用户数据并输出数组
public function read($id = '')
{
$user = UserModel::get($id);
dump($user->append(['user_status'])->toArray());
}

输出JSON

对于 API 开发而言,经常需要返回 JSON 格式的数据,修改 read 操作方法改成 JSON 输出:

1
2
3
4
5
6
// 读取用户数据输出JSON
public function read($id = '')
{
$user = UserModel::get($id);
echo $user->toJson();
}

七、视图和模板

前面只是在控制器方法里面直接输出而没有使用视图模板功能,从现在开始来了解下如何把变量赋值到模板,并渲染输出,主要内容包括:

  • 模板输出
  • 分页输出
  • 公共模板
  • 模板定位
  • 模板布局
  • 渲染内容
  • 助手函数

模板输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
namespace app\index\controller;
use app\index\model\User as UserModel;
use think\Controller;
class User extends Controller
{
// 获取用户数据列表并输出
public function index()
{
$list = UserModel::all();
$this->assign('list', $list);
$this->assign('count', count($list));
return $this->fetch();
}
}

image.png
assign 方法可以把任何类型的变量赋值给模板,关键在于模板中如何输出,不同的变量类型需要采用不同的标签输出。fetch 方法默认渲染输出的模板文件应该是当前控制器和操作对应的模板,也就是:

1
application/index/view/user/index.html

接下来,定义视图文件的内容,采用 volist 标签输出数据集:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>查看用户列表</title>
<style>
body{
color: #333;
font: 16px Verdana, "Helvetica Neue", helvetica, Arial, 'Microsoft YaHei', sans-serif;
margin: 0px;
padding: 20px;
}
a{
color: #868686;
cursor: pointer;
}
a:hover{
text-decoration: underline;
}
h2{
color: #4288ce;
font-weight: 400;
padding: 6px 0;
margin: 6px 0 0;
font-size: 28px;
border-bottom: 1px solid #eee;
}
div{
margin:8px;
}
.info{
padding: 12px 0;
border-bottom: 1px solid #eee;
}
.copyright{
margin-top: 24px;
padding: 12px 0;
border-top: 1px solid #eee;
}
</style>
</head>
<body>
<h2>用户列表({$count})</h2>
{volist name="list" id="user" }
ID:{$user.id}
昵称:{$user.nickname}
邮箱:{$user.email}
生日:{$user.birthday}
{/volist}
</body>
</html>

index方法给模板赋值了两个变量 count 和 list ,分别是标量和二维数组,标量的输出很简单,使用:{$count} 便可,一看就明白。二维数组通常使用 volist 标签输出,在volist 标签的 name 属性就是模板变量的名称, id 属性则是定义每次循环输出的变量,在volist标签中间使用 {$user.id} 表示输出当前用户的id属性,以此类推下面的内容则依次输出用户的相关属性。

分页输出

1
2
3
4
5
6
7
8
// 获取用户数据列表
public function index()
{
// 分页输出列表 每页显示3条数据
$list = UserModel::paginate(3);
$this->assign('list',$list);
return $this->fetch();
}

公共模板

加上之前定义的创建用户的模板,现在已经有两个模板文件了,为了避免重复定义模板,可以把模板的公共头部和尾部分离出来,便于维护。
我们把模板文件拆分为三部分:

1
2
3
4
//也就是把html文件分割
application/index/view/user/header.html
application/index/view/user/index.html
application/index/view/user/footer.html

index.html 内容为:

1
2
3
4
5
6
7
8
9
10
11
{include file="user/header" /}
<h2>用户列表({$count})</h2>
{volist name="list" id="user" }
<div class="info">
ID:{$user.id}<br/>
昵称:{$user.nickname}<br/>
邮箱:{$user.email}<br/>
生日:{$user.birthday}<br/>
</div>
{/volist}
{include file="user/footer" /}

模板定位

fetch 方法的第一个参数表示渲染的模板文件或者模板表达式。通常我们都是使用的模板表达式,而不需要使用完整的文件名。
模板文件名可以随意命名,如果把 index.html 文件改成:

1
2
3
4
5
6
7
8
9
application/index/view/user/list.html

index操作方法中的fetch方法需要改成:
return $this->fetch('list');
而如果fetch方法改成
return $this->fetch('index/list');
那么实际渲染的模板文件则是
application/index/view/index/list.html
同理多级。

模板布局

现在使用模板布局来进一步简化模板定义。
首先需要定义一个布局模板文件,放到 application/index/view/layout.html 内容如下:

1
2
3
{include file="user/header" /}
{__CONTENT__}
{include file="user/footer" /}

application/index/view/user/index.html 改成:

1
2
3
4
5
6
7
8
9
10
{layout name="layout" /}
<h2>用户列表({$count})</h2>
{volist name="list" id="user" }
<div class="info">
ID:{$user.id}<br/>
昵称:{$user.nickname}<br/>
邮箱:{$user.email}<br/>
生日:{$user.birthday}<br/>
</div>
{/volist}

在index模板文件中开头定义 layout 标签 ,表示当前模板使用了布局,布局模板文件为 layout.html ,布局模板中的 {__CONTENT__} 会自动替换为解析后的index.html内容。

渲染内容

有时候,并不需要模板文件,而是直接渲染内容或者读取数据库中存储的内容,控制器方法修改如下:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php
namespace app\index\controller;
use app\index\model\User as UserModel;
use think\Controller;
class User extends Controller
{
// 获取用户数据列表并输出
public function index()
{
$list = UserModel::all();
$this->assign('list', $list);
$this->assign('count', count($list));
// 关闭布局
$this->view->engine->layout(false);
$content = <<<EOT
<style>
body{
color: #333;
font: 16px Verdana, "Helvetica Neue", helvetica, Arial, 'Microsoft YaHei', sans-serif;
margin: 0px;
padding: 20px;
}
a{ color: #868686; cursor: pointer; }
a:hover{ text-decoration: underline; }
h2{
color: #4288ce;
font-weight: 400;
padding: 6px 0;
margin: 6px 0 0;
font-size: 28px;
border-bottom: 1px solid #eee;
}
div{
margin:8px;
}
.info{
padding: 12px 0;
border-bottom: 1px solid #eee;
}
.copyright{
margin-top: 24px;
padding: 12px 0;
border-top: 1px solid #eee;
}

<h2>用户列表({\$count}</h2>
<div>
{volist name="list" id="user" } ID:{\$user.id}
昵称:{\$user.nickname}
邮箱:{\$user.email}
生日:{\$user.birthday}
------------------------
{/volist}
</div>

EOT;
return $this->display($content);
}
}

display 方法用于渲染内容而不是模板文件输出,和直接使用 echo 输出的区别是 display 方法输出的内容支持模板标签的解析。

助手函数

可以使用系统提供的助手函数view简化模板渲染输出(注意不适用于内容渲染输出):
前面的模板渲染代码可以改为:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
namespace app\index\controller;
use app\index\model\User as UserModel;
class User
{
// 读取用户数据
public function read($id='')
{
$user = UserModel::get($id);
return view('', ['user' => $user], ['__PUBLIC__' => '/static']);
}
}

八、调试和日志

项目开发的时候,出现错误在所难免,最大的困惑在于发现问题所在,其次才是如何解决问题。因此懂得如何调试和跟踪问题非常之关键, 5.0 版本提供了非常方便的调试工具和手段,让你更容易定位和发现问题。

  • 第一式:未雨绸缪——页面Trace
  • 第二式:初见端倪——异常页面
  • 第三式:拨云见日——断点调试
  • 第四式:欲穷千里——日志分析
  • 第五式:运筹帷幄——远程调试

第一式:未雨绸缪——页面Trace

  • 页面 Trace 的主要作用包括:
  • 查看运行数据;
  • 查看文件加载情况;
  • 查看运行流程;
  • 查看当前执行SQL;
  • 跟踪调试数据;
  • 查看页面错误信息;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //config.php

    // 开启应用Trace调试
    'app_trace' => true,
    // 设置Trace显示方式
    'trace' => [
    // 在当前Html页面显示Trace信息
    'type' => 'html',
    ]

如果你不希望影响页面的输出效果,可以启用浏览器Trace调试信息,设置如下:

1
2
3
4
5
6
7
// 开启应用Trace调试
'app_trace' => true,
// 设置Trace显示方式
'trace' => [
// 使用浏览器console显示页面trace信息
'type' => 'console',
],

Ajax方式请求的信息不会在页面Trace中显示,还包括部分页面Trace之后执行的日志信息也无法在Trace中查看到。
由于本人不搞开发,就有这个就够用了,后面四式改日再学。

九、命令行工具

生成模块

下面我们给应用生成一个新的模块 test ,首先需要在 application 目录下面创建一个 build.php 定义文件,文件内容如下:

1
2
3
4
5
6
7
8
9
return [
// 定义test模块的自动生成
'test' => [
'__dir__' => ['controller', 'model', 'view'],
'controller' => ['User', 'UserType'],
'model' => ['User', 'UserType'],
'view' => ['index/index', 'index/test'],
],
];

然后在命令行下面,执行:

1
2
3
4
要在think文件目录下。。。

>php think build
Successed

我们可以看到 application 目录下面已经生成了一个 test 模块目录,包括下面的子目录及文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
test
├─controller
│ ├─Index.php
│ ├─User.php
│ └─UserType.php
├─model
│ ├─User.php
│ └─UserType.php
├─view
│ └─index
│ ├─index.html
│ └─test.html
├─common.php
└─config.php

我们在 build.php 文件中并没有定义 Index 控制器,但仍然生成了一个默认的 Index 控制器文件以及欢迎页面,这是为了避免模块访问出错。
image.png

生成文件

还可以用 make 指令单独生成某个应用类库文件,例如:

1
2
php think make:controller test/Blog
php think make:controller test/Blog --plain

会自动为test模块生成一个 Blog控制器文件。

注意,默认生成的控制器类是属于资源控制器,并且继承了\think\Controller。

生成类库映射文件

在生成类库文件之后,我们强烈建议使用命令行生成类库映射文件,可以提高自动加载的性能,用法如下:

1
>php think optimize:autoload

执行完毕,会在 RUNTIME_PATH 目录下面生成一个 classmap.php 文件,包括了系统和应用的所有类库文件的映射列表。

生成路由缓存文件

如果你的应用定义了大量的路由规则,那么建议在实际部署后生成路由缓存文件,可以免去路由注册的开销,从而改善路由的检测效率,用法如下:

1
2
3
4
5
>php think optimize:route
​```执行完毕,会在 RUNTIME_PATH 目录下面生成一个 route.php 文件,包括了应用的所有路由规则定义列表。

## 生成数据表字段缓存文件
如果你希望提高查询的性能,可以通过生成字段缓存来减少查询。用法如下:
1
2
3
4
5
6
7
8
9
>php think optimize:schema

>php think optimize:schema --db demo //生成demo数据库下面的所有数据表的字段缓存信息。需要权限

>php think optimize:schema --module index //index模块的模型来生成数据表字段缓存 读取模块的模型类来生成数据表字段缓存(这个适合多数据库连接的情况)

php think optimize:schema --table think_user //更新某个数据表的缓存

php think optimize:schema --table demo.think_user //指定数据库名称

执行完毕,会在 RUNTIME_PATH 目录下面创建schema目录,然后在该目录下面按照database.table.php 的文件命名生成数据表字段缓存文件。

清理缓存目录的指令 clear

首先,我们创建一个指令类 app\console\Clear ,内容如下:

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
<?php
namespace app\console;
use think\console\command\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
class Clear extends Command
{
protected function configure()
{
// 指令配置
$this
->setName('clear')
->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null)
->setDescription('Clear runtime file');
}
protected function execute(Input $input, Output $output)
{
$path = $input->getOption('path') ?: RUNTIME_PATH;
$files = scandir($path);
if ($files) {
foreach ($files as $file) {
if ('.' != $file && '..' != $file && is_dir($path . $file)) {
array_map('unlink', glob($path . $file . '/*.*'));
}
elseif (is_file($path . $file)) {
unlink($path . $file);
}
}
}
$output->writeln("Clear Successed");
}
}

然后,在application目录下面的 command.php (如果不存在则创建)文件中添加如下内容:

1
2
3
return [
'\app\console\Clear',
];

测试下该指令:

1
2
3
4
5
6
7
>php think clear
Clear Successed

>php think clear --path d:\www\tp5\runtime\log\
>php think clear -d d:\www\tp5\runtime\log\ //--path 参数用于指定目录

该指令并不会删除目录,仅仅删除目录下面(包括子目录)的文件

十、杂项

Session

Session 初始化

ThinkPHP5 会在第一次调用 Session 类的时候按
照配置的参数自动初始化和开启 Session (如果 auto_start 设置为 true 的话),例如,我们在应用
配置中添加如下配置:

1
2
3
4
5
6
7
8
9
'session' => [
'prefix' => 'think',
'type' => '',
'auto_start' => true,
],

无需任何操作就可以直接调用 Session 类的相关方法,例如:
Session::set('name','thinkphp');
Session::get('name');

读取 Session

建议的读取 Session 数据的方法是通过 Request 请求对象的 session 方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
namespace app\index\controller;
use think\Request;
class User
{
public function index(Request $request)
{
echo $request->session('user_name');
// 读取二维数组
echo $request->session('user.name');
}
}

也支持使用 Session 类直接读取数据:
class User
{
public function index()
{
echo Session::get('user_name');
echo Session::get('user.name');
}
}

通过Request对象读取Session数据支持默认值及过滤方法,因此也更加安全,并且支持多维数组的读取

Session 类的 get 方法只支持最大二维数组的读取,而 Request 对象的 session 方法可以支持任意级别的二维数组获取。

SESSION 操作

使用 think\Session 类进行 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
namespace app\index\controller;
use think\Session;
class user
{
public function index()
{
// 赋值(当前作用域)
Session::set('name','thinkphp');
// 赋值think作用域
Session::set('name','thinkphp','think');
// 判断(当前作用域)是否赋值
Session::has('name');
// 判断think作用域下面是否赋值
Session::has('name','think');
// 取值(当前作用域)
Session::get('name');
// 取值think作用域
Session::get('name','think'); //如果name的值不存在,返回null。
// 删除(当前作用域)
Session::delete('name');
// 删除think作用域下面的值
Session::delete('name','think');
Session::prefix('think');
// 取值并删除
Session::pull('name'); //如果name的值不存在,返回Null。
// 清除session(当前作用域)
Session::clear();
// 清除think作用域
Session::clear('think');
// 设置session 并且在下一次请求之前有效
Session::flash('name','value');
// 清除当前请求有效的session
Session::flush();

//支持session的二维数组操作,例如:
// 赋值(当前作用域)
Session::set('name.item','thinkphp');
// 判断(当前作用域)是否赋值
Session::has('name.item');
// 取值(当前作用域)
Session::get('name.item');
// 删除(当前作用域)
Session::delete('name.item');
}

模板输出

1
2
3
4
如果需要在模板中输出Session数据,可以使用下面的方法:
{$Request.session.user_name}
也可以支持二维数组的输出
{$Request.session.user.name}

初始化

大多数情况下,我们不需要进行 Cookie 的初始化,系统会在调用 Cooie 类方法的时候自动根据 cache配置参数初始化,如果需要可以手动使用 init 方法进行初始化设置,例如:(跟session差不多)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// cookie初始化
Cookie::init(['prefix'=>'think_', 'expire'=>3600, 'path'=>'/']);
// 单独指定当前前缀
Cookie::prefix('think_');

// cookie 名称前缀
'prefix' => '',
// cookie 保存时间
'expire' => 0,
// cookie 保存路径
'path' => '/',
// cookie 有效域名
'domain' => '',
// cookie 启用安全传输
'secure' => false,
// httponly设置
'httponly' => '',
// 是否使用 setcookie
'setcookie' => true,

读取Cookie

1
2
3
4
5
6
7
8
9
10
11
namespace app\index\controller;
use think\Request;
class User
{
public function index(Request $request)
{
echo $request->cookie('user_name');
// 读取二维数组
echo $request->cookie('user.name');
}
}

通过Request对象读取Cookie数据支持默认值及过滤方法,因此也更加安全,并且支持多维数组的读取。

模板输出

1
{$Request.cookie.user_name}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 设置Cookie 有效期为 3600秒
Cookie::set('name','value',3600);
// 设置cookie 前缀为think_
Cookie::set('name','value',['prefix'=>'think_', 'expire'=>3600]);
// 支持数组
Cookie::set('name',[1,2,3]);

Cookie::has('name');
// 判断指定前缀的cookie值是否存在
Cookie::has('name','think_');

Cookie::get('name');
// 获取指定前缀的cookie值
Cookie::get('name','think_');

Cookie::delete('name');
// 删除指定前缀的cookie
Cookie::delete('name','think_');

// 清空指定前缀的cookie
Cookie::clear('think_');

验证码

安装类库

在使用验证码之前,必须使用 Composer 来安装验证码类库,在命令行下面切换到你的应用根目录下面,执行:

1
composer require topthink/think-captcha

THE END

学了一天半的thinkphp5,总体了解了框架的大体架构,等明天考完试开始试着分析一下最近出了牛逼的两个RCE洞,这里祝明天考试一切顺利吧,也祝愿她考试一切顺利吧。此篇虽然只是入门文章,但是还没写完,未完待续ING。

-------------本文结束感谢您的阅读-------------