首页 » PHP » 正文

thinkphp5学习笔记(7)

(3)读取器和修改器

读取器

前面读取用户生日的时候,使用了 date 方法进行日期的格式处理输出,但是每次读取数据后都需要这样处
理就显得非常麻烦。
使用读取器功能就可以简化类似的数据处理操作,例如,我们给 User 模型添加读取器的定义方法。

<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// birthday读取器
protected function getBirthdayAttr($birthday)
{
return date('Y-m-d', $birthday);
}
}

这里,我们添加了一个 getBirthdayAttr 读取器方法用于读取 User 模型的 birthday 属性的值,该方
法会在读取birthday属性值的时候自动执行。

读取器方法的命名规范是:

get + 属性名的驼峰命名+ Attr

所以, getBirthdayAttr 读取器读取的是 birthday 属性,而 getUserBirthdayAttr 读取器读取的则是 user_birthday 属性。
定义完修改器后,修改控制器的 read 操作方法如下:

// 读取用户数据
public function read($id='')
{
$user = UserModel::get($id);
echo $user->nickname . '<br/>';
echo $user->email . '<br/>';
echo $user->birthday . '<br/>';
}

读取器还可以定义读取数据表中不存在的属性,例如把原始生日和转换的格式分开两个属性 birthday 和
user_birthday ,我们只需定义 user_birthday 属性的读取器方法:

<?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 参数获取。
read操作方法修改为:

// 读取用户数据
public function read($id='')
{
$user = UserModel::get($id);
echo $user->nickname . '<br/>';
echo $user->email . '<br/>';
echo $user->birthday . '<br/>';
echo $user->user_birthday . '<br/>';
}

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

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

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

<?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);
}
}

修改器方法的命名规范是:

set + 属性名的驼峰命名+ Attr

所以, setBirthdayAttr 方法修改的是 birthday 属性,而 setUserBirthdayAttr 方法修改的则是
user_birthday 属性。
控制器的 add 操作方法修改如下:

// 新增用户数据
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 . ' ]新增成功';
} else {
return $user->getError();
}
}

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

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

<?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 ,或者也可以显示的设置日期格式,例如:

<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
protected $type = [
// 设置birthday为时间戳类型(整型)
'birthday' => 'timestamp:Y/m/d',
];
}
对于简单的数据格式转换之类的处理,设置类型转换比定义修改器和读取器更加方便。

qq%e5%9b%be%e7%89%8720161207104106

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

// 开启自动写入时间戳字段
'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;
}

默认的时间戳字段类型是整型,如果需要使用其它的时间字段类型,可以做如下设置:

<?php
namespace app\index\model;
use think\Model;
class User extends Model
{
// 定义类型转换
protected $type = [
'birthday' => 'timestamp:Y/m/d',
];
// 指定自动写入时间戳的类型为dateTime类型
protected $autoWriteTimestamp = 'datetime';
}

如果全局的自动时间戳的类型是统一的,也可以直接在数据库配置文件中设置:

// 开启自动写入时间戳字段
'auto_timestamp' => 'datetime',

如上设置后,你的 think_user 数据表中的 create_time 和 update_time 字段类型就必须更改为
datetime 类型的格式。
支持设置的时间戳类型包含: datetime 、 date 和 timestamp 。

自动完成

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

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

qq%e5%9b%be%e7%89%8720161207104106

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

(5)查询范围

对于一些常用的查询条件,我们可以封装成查询范围来进行方便的调用。
例如,邮箱地址为 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);
}
}
查询范围方法的定义规范为:
scope + 查询范围名称

我们修改控制器的index方法如下:

// 根据查询范围获取用户数据列表
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 方法,并且可以追加新的查询及链式操作,例如:

// 根据查询范围获取用户数据列表
public function index()
{
//
$list = User::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/>';
}
}

上面的scope方法使用了闭包,闭包里面支持所有的链式操作方法。
最后生成的SQL语句是:
SELECT * FROM `think_user` WHERE `email` = ‘thinkphp@qq.com’ AND `status` = 1 ORDER BY
`id` desc

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

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

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

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

全局查询范围

可以给模型定义全局的查询范围,在模型类添加一个静态的 base 方法即可,例如我们给模型类增加一个全
局查询范围,用于查询状态为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');
}
// 全局查询范围
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 ,内容如下:

<!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 如下:

// 创建用户数据页面
public function create()
{
return view();
}

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

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

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

// 新增用户数据
public function add()
{
$user = new UserModel;
if ($user->allowField(true)->save(input('post.'))) {
return '用户[ ' . $user->nickname . ':' . $user->id . ' ]新增成功';
} else {
return $user->getError();
}
}
这里使用 allowField(true) 是为了避免表单令牌验证的字段被写入数据表,如果你已经在模型里面定义了field属性的话,可以不需要。

表单验证

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

<?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',
];
}

User 验证器添加了三个属性的验证规则,分别表示:
1昵称必须,而且最小长度为5
2邮箱必须,而且必须是合法的邮件地址
3生日可选,如果填写的话必须为 Y-m-d 格式的日期格式

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

如果我们的验证规则里面使用了 | ,为了避免混淆则必须用数组方式定义验证规则,验证规则定义修改如
下:

<?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'],
];
}

然后对控制器的 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();
}
}

如果希望完整定义错误提示信息的话,可以使用:

<?php
namespace app\index\validate;
use think\Validate;
class User extends Validate
{
// 验证规则
protected $rule = [
['nickname', 'require|min:5', '昵称必须|昵称不能短于5个字符'],
['email', 'email', '邮箱格式错误'],
['birthday', 'dateFormat:Y-m-d', '生日格式错误'],
];
}

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

<?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)
{
return 1 === preg_match('/^\w+([-+.]\w+)*@' . $rule . '$/', $value);
}
}

自定义验证规则也支持返回动态的错误信息,只需要在验证方法里面返回错误信息字符串即可,例如:

<?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;
}
}
}

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

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字段单独
验证是否是一个有效的日期格式:

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 . ' ]新增成功';
}
}
博主的文章或程序如果给您带来了价值,感谢您打赏一二
微信扫码支付 支付宝扫码支付

发表评论