前言
框架只是工具,无需在意版本,纠结用法,只要工具喜欢,精通一个,仍能做成一个精美的软件。
规则
目录名:小写+下划线
类名:首字母大写,驼峰,都.php后缀
函数/方法/属性:首字母小写,驼峰
控制器
可继承BaseController, 已经注入了think\app , think\Request.
空控制器
单应用下:创建Error控制器,index方法。默认报错到这里,后面自行输出报错。
可以在config/app.php文件定义 HTTP异常页面:
'http_exception_template' => [
// 定义404错误的重定向页面地址
404 => \think\facade\App::getRootPath() . 'public\404.html',
// 还可以定义其它的HTTP status
401 => \think\facade\App::getRootPath() . 'public\401.html'
],
index控制器
输出状态码,自动跳到配的错误页。
namespace app\controller;
use app\BaseController;
Class Error extends BaseController{
public function index()
{
throw new HTTPException(404);
}
}
数据库
配置
测试:.env文件
部署:在 config/databases.php里写,还可以配置多个数据库
连接
table: 写完整表名 , name: 需配置前缀 prefix ,然后忽略前缀写
$res = Db::connect('数据库名')->table('完成表名')->select();
$res = Db::connect('数据库名')->table('表名')->select();
return json($res);
查询
单条:find findOrFail, findOrEmpty ( 有查询到就显示一条没有就显示空,相较于find用这个吧 ), 需加上where
Db::table('tp_user')->where('id', '>', '1')->findOrEmpty()
多条: select, selectOrFail(), toArray(), column()
$res = Db::table('tp_user')->selectOrFail()->column('usernanme', 'id');//查指定字段
if (!$res->isEmpty()) {
$res = $res->toArray();
}
分批查:chunk 避免大数据一次性读取有可能会导致内存开销过大
Db::table('tp_user')->chunk(3, function(){
foreach($users as $user) {
dump($user);
}
})
保留对象查
长连接,节约资源,注意要用Db::name不能用Db::table, 下次查询加上 removeOption()
$Db = Db::name('user');
$res1 = $Db->selectOrFail()->toArray();
$res2 = $Db->removeOption()
->selectOrFail()->toArray();
增
insert,save, insertGetId, insertAll
public function addDataToSql()
{
$dbObj = Db::table('tp_user');
//单数据
$data = [
'username' => '辉夜',
'password' => '123',
'gender' => '女',
'email' => 'huiye@163.com',
'price' => 90,
'details' => '123'
];
//成功返回: 1
echo $dbObj->insert($data);
echo $dbObj->save($data);
//成功返回: ID
echo $dbObj->removeOption()->insertGetId($data);
//若不存在的字段数据,仍强插
echo $dbObj->removeOption()->strict('false')->insert($data);
//多数据
$dataArr = [
[
'username' => '辉夜',
'password' => '123',
'gender' => '女',
'email' => 'huiye@163.com',
'price' => 90,
'details' => '123'
],
[
'username' => '辉夜',
'password' => '123',
'gender' => '女',
'email' => 'huiye@163.com',
'price' => 90,
'details' => '123'
]
];
echo Db::table('tp_user')->insertAll($dataArr);
}
删
delete
//删除数据: delete 删了(影响)几行回复几个
public function deleteData() {
$dbQuery = Db::name('user');
echo $dbQuery->delete('309');
echo $dbQuery->removeOption()->delete([307,308,306]);
echo $dbQuery->removeOption()->where('id', '=', '301')->delete();
}
改
update
public function updateDate()
{
$dbQuery = Db::name('user');
$upName = [
'username' => '李白'
];
//成功1,未修改0
echo $dbQuery->where('id','=','306')->update($upName);
//自增/自减步长, mysql的函数等等
echo $dbQuery->removeOption()->where('id','=','29')
->update([
'email' => Db::raw('UPPER(email)'),
'price' => Db::raw('price + 1'),
'uid' => Db::raw('uid - 2'),
'status'=> Db::raw('11')
]);
}
查
基本查
//查询表达式 =, !=, > , < , >=, <= , Between , Like, In, Null
//自行拼装: exp
//whereLike , whereNotLike, whereBetween ,whereNotBetween, whereIn, whereNotIn, whereNull, whereNotNull
public function selectTypeMonth() {
$dbQuery = Db::name('user');
dump($dbQuery->where('id','<>', '1')->select()->toArray());
//组合
dump($dbQuery->removeOption()->where('email','like',['wu%','LIBA'], 'or')->select()->toArray());
//whereNotLike
dump($dbQuery->removeOption()->whereLike('email', 'wu%')->select()->toArray());
//whereNotBetween
dump($dbQuery->removeOption()->whereBetween('id','19,20')->select()->toArray());
//whereNotIn
dump($dbQuery->removeOption()->whereIn('id','19,20,21')->select()->toArray());
//whereNotNull
dump($dbQuery->removeOption()->whereNull('email')->select()->toArray());
//自行拼装
dump($dbQuery->removeOption()->where('email', 'exp', 'like \'wu%\' or \'LI%\' ')->select()->toArray());
}
聚合查询
系统提供更方便的列计算工具 count(), max, min, avg, sum
public function poly() {
//所以数据数量, 若指定字段: 用户表有填写邮箱的总数,nulL则忽略。
$res = Db::name('user')->count();
$res = Db::name('user')->count('email');
//字段最大值(强制转换false)
$maxVal = Db::name('user')->max('price', false);
//字段最小值
$minVal = Db::name('user')->min('uid');
//字段平均值
$midVal = Db::name('user')->avg('id');
//字段总和
$tolVal = Db::name('user')->sum('id');
dump($tolVal);
}
子查询
buildSql() 创建子查询语句
public function sonSearch()
{
//在two表中查出男性的UID,在one表中查询男性的信息
$buildSQlVal = Db::name('two')->field('uid')->where('gender', '男')->buildSql();
$resSQl = Db::name('one')->where('id','exp', 'IN '. $buildSQlVal)
->select()->toArray();
dump($resSQl);
}
原生查询
query(仅查询), execute (表操作)
Db::query()
Db::execute()
链式查询(1)
where , field, alisa
public function uplink() {
//建议都这样写,条件清晰,最终组合至where里去
$map[] = ['gender', '=', '男'];
$map[] = ['price' , '>', '100'];
//where: 条件
$res = Db::table('tp_user')->where($map)->select()->toArray();
//field: 指定查询字段. 可取别名、设置函数等。
$fieldSql = Db::table('tp_user')
->field('id, SUM(price), gender as sex')
->where($map)->select()->toArray();
//也可直接without(不要的字段列表)
$fieldSql1 = Db::table('tp_user')->withoutField('id, price, sex')->select()->toArray();
//alisa: 取别名
$alisa = Db::name('user')->alias('a')->select();
dump($fieldSql1);
}
链式查询(2)
limit , page, order, group, having
public function uplink2() {
//限制
$res = Db::table('tp_user')->limit(5)->select()->toArray();
//分页用page(哪一页,一页几条)
$res = Db::table('tp_user')->page(0,5)->order('id','ASC')->select()->toArray();
//排序 支持数组写入
$res = Db::table('tp_user')->order([ 'price'=>'desc', 'id' => 'asc'])->select()->toArray();
//Mysql函数排序 orderRow: 按username排序的同时把 username为黑崎一护的行 始终排到第一行
$res = Db::table('tp_user')->orderRaw('FIELD(username, "黑崎一护") DESC')->select()->toArray();
//分组: 分别计算男,女的价格/成绩/积分等等总和, 是分别哦 group
$res = Db::table('tp_user')->field('gender, SUM(price)')->group('gender')->select()->toArray();
//having: 在分组之后的再筛选: 分别计算男,女的价格/成绩/积分等等总和,并且只统计存在大于100分的价格/成绩/积分才统计。
$res = Db::table('tp_user')->field('gender, SUM(price) as a')->group('gender')->having('a > 100')->select()->toArray();
dump($res);
}
WhereRaw
组合查询:特简单
$map作为where条件的用法
1:
$map1 = [
['username', 'like', '%X%'],
['email', 'like', '%xx'],
]
$map2 = [
['username', 'like', '%X%'],
['email', 'like', '%xx'],
]
Db::table(xx)->whereOr($map1, $map2);
SELECT * FROM `tp_user` WHERE
( `username` LIKE '%小%' AND `email` LIKE '%163%' )
OR
( `username` LIKE '%孙%%' AND `email` LIKE '%com%' )
2:
$map[] = ['id', '1'];
$map[] = ['price', 'exp', Db::raw('>100')];
$map[] = ['create_time', 'between', ['2016-06-27 16:45:26', '2023-01-01 01:01:01']];
Db::table(xx)->where($map)->xxx;
3. WhereRaw直接写条件
Db::table(xx)->whereRow("( `username` LIKE '%小%' AND `email` LIKE '%163%' ) OR ( `username` LIKE '%孙%%' AND `email` LIKE '%com%' )");
WhereColumn
比较字段的值
xxx -> whereColumn('update_time' , '>', 'create_time')->xxx
事务
Db::StartTrans();
try {
//支付他三块钱
Db::table(xx)->where('id','1')->update(['price' => Db::raw('price + 3')]);
//调用API,确认已经收到三块钱。
Db::table(xx)->where('id','2')->update(['price' => Db::raw('price - 3')]);
//确认其他逻辑
Db::commit();
} catch (\Exception $e) {
Db::rollback();
Throw new HTTPException(404);
}
模型
遵循mvc,尽量在model层操作数据。
说明
模型会自动对应数据表
配置前缀prefix, 创建对应数据库表tp_user,即可在app目录下新建 model 文件夹,User.php类。
操作与前面数据库没太大差别
属性
//软删除与字段
use SoftDelete;
protected $deleteTime = 'delete_time';
//表名
protected $name = 'user';
//主键
protected $pk = 'id';
//模式
protected $strict = 'true';
//json字段:特殊的要写出来
protected $json = ['list'];
//只读的字段: 无法被修改的字段
protected $readonly = ['id', 'uid'];
//忽略字段: 当某个字段在开发项目版本升级中不再使用
protected $disuse = ['notUseColumn'];
//强制定义类型: 后面控制器不用麻烦数据格式,查出来就自动转格式
protected $type = [
'price' => 'integer',
'status'=> 'boolean'
];
//初始化
protected static function init()
{
parent::init(); // TODO: Change the autogenerated stub
echo "init中,初始化User模型" . "<hr/>";
}
增
create,saveAll
public function saveAllData(array $data)
{
return self::create($data);
return $this->saveAll($data);
}
改
一条数据就找到对应的数据集后操作
$changeRow = self::where($option, $optVal)->find();
return $changeRow->save($data);
更新全部数据, 必须含有主键ID
$changeDataArr = [
[
'id' => '320',
'username' => '白 + 黑',
'email' => 'test@qq.com',
'price' => '999'
],
[
'id' => '322',
'username' => '白 + 黑',
'email' => 'test@qq.com',
'price' => '999'
],
];
return $this->saveAll($changeDataArr);
删
条件删一条或直接传主键数组删多个
return self::where('id', '=', $id)->delete();
return self::destroy($idArr);
软删除
很有用,例如关联表,A表硬删除了B表就关联不到了.
就不是真的删,就是数据库多个字段记录它被删了。
现要在model属性设置 软删除与字段
use SoftDelete;
protected $deleteTime = ‘delete_time’;
删
return self::destroy($id);
带上被删除的数据显示
return self::withTrashed()->select();
查
public function searchByOpt($opt)
{
$res = self::where($opt)->select();
if (!$res->isEmpty()) {
return $res->toArray();
} else {
return false;
}
}
Json
json字段处理: 都需要特别在链式指出 在模型层的属性指定
控制器端:
public function jsonFin()
{
$User = new TestUserModel();
$data = [
'username' => 'vechis',
'password' => 'wwa',
'gender' => '男',
'email' => 'vechis@163.com',
'price' => '1000',
'details' => '312',
'list' => ['username'=>'辉夜', 'gender'=>'女', 'email'=>'huiye@163.com']
];
//增json数据
dump($User->json(['list'])->save($data));
//更新json数据
dump($User->where('id', '326')->json(['list'])->update($data));
//查询json数据
dump($User->where('id','326')->json(['list'])->select()->toArray());
}
获取器
就是把取出来的数据在返回之前自动处理。比如数据库存字段tinyint, 就存0,1,2,3,取得时候自带原有的中文
规则: get + 字段名(驼峰) + Attr
/**
* 获取器
* @param $value
* @return string
*/
public function getStatusAttr($value)
{
$status = [-1=>'删除', 0=>'禁用', 1=>'正常', 2=>'待审核'];
return $status[$value];
}
修改器
从控制层到模型层的数据,经过模型层写的修改器方法,将要修改的数据自动处理
规则: set+ 字段名(驼峰) + Attr
控制器:
public function setToModel()
{
$changeData = [
'id' => '323',
'gender' => '女',
'username' => 'shuiy',
'email' => 'xxx@dds.com'
];
$User = new TestUserModel();
$User->updateOne('id','323',$changeData);
}
模型层:
/**
* 修改器
* @param $value
* @return string
*/
public function setEmailAttr($value)
{
return strtoupper($value);
}
事件
增删改查的时候,模型层额外记录这个事儿的一些操作。
protected的,静态的方法, on+ 固定的事件名 , $query
删除后: after_delete
protected static function onAfterDelete($query) {
//获取请求的ip地址
$ip = request()->ip();
//谁删的
$no = Session::get('Userno');
//执行
echo "工号" . $no . "IP为" . $ip . ”执行了语句“ . $query;
}
其他:
查询后:after_read
新增后: after_insert
更新后:after_update
关联
父子表必须含有外键连在一起。
父表在model写个函数声明, 即可在控制器中直接使用
主要根据数据库表设计的三大范式,分为父表和多个子表,引申现在的关联。
目的:能根据父表统一操作子表数据,以便完成莫须有的方便。
一对一
父表 1条数据 对应 子表 1条数据
hasOne
public function profile()
{
// 子表模型类, 外键,子表主键
return $this->hasOne(Profile::class, 'user_id', 'id');
}
控制器端
public function oneByoneFromUser()
{
$User = new UserFather();
//查
dump($User->where('id','20')->findOrEmpty()->profile->hobby);
//增
dump($User->where('id','20')->findOrEmpty()->profile()->save([
'hobby' => '特爱吃肉2',
'status' => 3
]));
//改
dump($User->where('id','20')->findOrEmpty()->profile()->update([
'hobby' => '不爱吃肉',
'status' => '3'
]));
//删
dump($User->where('id', '326')->findOrEmpty()->profile()->delete());
}
一对多
hasMany
意思是父表对应子表中,有多条数据。
模型端:
public function intellInfo()
{
return $this->hasMany(IntellInfo::class, 'Info_id', 'id');
}
控制器端
public function oneByManyFromIntell()
{
$Intell = new Intell();
//增
dump($Intell->where('id', '4')->findOrEmpty()->intellInfo()->saveAll([
['user' => '卿', 'dept' => 'IT', 'type' => '6', 'apply_time' => '2023-01-01', 'acc_time' => '2023-10-05'],
['user' => '宋', 'dept' => 'IT', 'type' => '7', 'apply_time' => '2023-02-01', 'acc_time' => '2023-10-05']
]));
//删
dump($Intell->where('id','4')->findOrEmpty()->intellInfo()->delete(30));
//改
dump($Intell->where('id','4')->findOrEmpty()->intellInfo()->where('id','31')->update(['user'=>'Huang']));
//查
dump($Intell->with(['IntellInfo', 'IntellReward'])->where('id','4')->select()->toArray());
}
多对多
直接用join,别费事。
关联预加载
不懂为啥叫的这么高级,其实就是为了节约在关联的时候查询多的资源,一开始想好要掉子表的时候,加个with关键字即可。
$list = Intell::whereRaw('id in (1,2,3)')->select();
=>
$list = Intell::with(['IntellInfo', 'IntellReward'])->where.....
中间件
就是一种:用户请求和控制器之间的处理机制。
可拦截和过滤HTTP 请求,URL 重定向、权限验证等。
创建
php think make:middleware Check
基本
文件:app - middleware - Check.php
必须返回:Response对象
根据传入的$request对象进行各种操作
前置中间件:在拿到 n e x t ( next( next(request)之前的操作
后置中间件:在拿到 n e x t ( next( next(request)之后的操作
结束: 中间件结束后会执行的操作。 加个end函数。
public function handle($request, \Closure $next)
{
$token = 5;
if ($request->param('token') == $token) {
echo "鉴权ok" . "<br/>";
}
echo "前置中间件" . "<br/>";
$response = $next($request);
echo "后置中间件" . "<br/>";
return $response;
}
public function end(Response $response)
{
//中间件结束执行
dump($response->getHeader());
}
使用
控制器端
直接定义属性。其中属性还可以细化哪个方法使用,不给哪个方法使用。
protected $middleware = ['app\middleware\Check'];
或者
protected $middleware = [
'app\middleware\Check' => ['only' => ['控制器的方法1', '控制器的方法2']],
'app\middleware\Auth' => ['except' => ['控制器的方法3']]
];
路由
尾部加个中间件类即可
Route::rule('test', 'XXXX@YYY')->middleware(app\middleware\Check::class, "可以传值")
=》中间件的handle方法中加个参数就可以拿到
public function handle($request, \Closure $next, $params)
事件
说个:事件监听类, 可以写个日志啥的比较方便
创建
php think make:listener TestListen
基本
文件:app - listener- TestListen.php
TestListen类: 可以做这些事:
public function handle($event)
{
dump("监听了XXX事件,根据session获取用户名,写入日志" . $event);
}
配置
app/even文件
如下:添加一个自定义监听的value,绑定刚创建的TestListen类
'listen' => [
'AppInit' => [],
'HttpRun' => [],
'HttpEnd' => [],
'LogLevel' => [],
'LogWrite' => [],
'TestListener' => [\app\listener\TestListener::class]
],
使用
控制器直接调用:
Event::trigger('TestListener');
或者直接在配置文件中 'HttpRun’绑定TestListen类,这样每个http请求在执行时候都会执行TestListen类
'HttpRun' => [\app\listener\TestListener::class],
验证器
就是验证各个数据是否匹配框架自带的各种验证工具
定义传入的参数,错误的提示信息
验证速查
格式验证类
'field' => 'require', //不得为空::isRequire 或::must
'field' => 'number', //是否是纯数字,非负数非小数点
'field' => 'integer', //是否是整数
'field' => 'float', //是否是浮点数
'field' => 'boolean', //是否是布尔值,或者bool
'field' => 'email', //是否是email
'field' => 'array', //是否是数组
'field' => 'accepted', //是否是“yes”“no”“1”这三个值
'field' => 'date', //是否是有效日期
'field' => 'alpha', //是否是纯字母
'field' => 'alphaNum', //是否是字母和数字
'field' => 'alphaDash', //是否是字母和数字以及_-(下划线和破折号)
'field' => 'chs', //是否是纯汉字
'field' => 'chsAlpha', //是否是汉字字母
'field' => 'chsAlphaNum', //是否是汉字字母数字
'field' => 'chsDash', //是否是汉字字母数字以及_-(下划线和破折号)
'field' => 'cntrl', //是否是控制字符(换行、缩进、空格)
'field' => 'graph', //是否是可打印字符(空格除外)
'field' => 'print', //是否是可打印字符(包含空格)
'field' => 'lower', //是否是小写字符
'field' => 'upper', //是否是大写字符
'field' => 'space', //是否是空白字符
'field' => 'xdigit', //是否是十六进制
'field' => 'activeUrl', //是否是有效域名或IP 地址
'field' => 'url', //是否是有效URL 地址
'field' => 'ip', //是否是有效IP(支持ipv4,ipv6)
'field' => 'dateFormat:Y-m-d', //是否是指定日期格式
'field' => 'mobile', //是否是有效手机
'field' => 'idCard', //是否是有效身份证
'field' => 'macAddr', //是否是有效MAC 地址
长度和区间验证类
'field' => 'in:1,2,3', //是否是有某个值
'field' => 'notIn:1,2,3', //是否是没有某个值
'field' => 'between:1,100', //是否是在区间中
'field' => 'notBetween:1,100', //是否是不在区间中
'field' => 'length:2,20', //是否字符长度在范围中
'field' => 'length:4', //是否字符长度匹配
'field' => 'max:20', //是否字符最大长度
'field' => 'min:5', //是否字符最小长度
'field' => 'after:2020-1-1', //是否在指定日期之后
'field' => 'before:2020-1-1', //是否在指定日期之前
//是否在当前操作是否在某个有效期内
'field' => 'expire:2019-1-1,2020-1-1',
//验证当前请求的IP 是否在某个范围之间,
'field' => 'allowIp:221.221.78.1, 192.168.0.1',
//验证当前请求的IP 是否被禁用
'field' => 'denyIp:221.221.78.1, 127.0.0.1',
其它验证类:
'field' => '\d{6}', //正则表达式验证
'field' => 'regex:\d{6}', //正则表达式验证
'field' => 'file', //判断是否是上传文件
'field' => 'image:150,150,gif', //判断图片(参数可选)
'field' => 'fileExt:jpg,txt', //判断文件允许的后缀
'field' => 'fileMime:text/html',//判断文件允许的文件类型
'field' => 'fileSize:2048', //判断文件允许的字节大小
单个
直接调用,返回bool
//字符串
$mailData = 'xx@xx.com';
dump(Validate::checkRule($mailData,'email|require|max:20|between:1,20'));
//日期
$date1 = '2023-01-01';
dump(Validate::checkRule($date1, 'after:2021-01-01|before:2025-01-01|dateFormat:Y-m-d|expire:2019-1-1,2020-1-1'));
//文件
$filePath = Request::file('file');
dump(Validate::checkRule($filePath, 'file|fileExt:txt|fileSize:1024|fileMime:text/plain'));
多个
控制器自定义小型验证器
public function verityNotWithValidate()
{
$validate = Validate::rule([
'name|用户名' => 'require|max:20',
'price|价格' => 'number|between:1,100',
'email|邮箱' => 'email'
]);
$validate->message([
'name.require' => '姓名不得为空',
'name.max' => '姓名不得大于20位',
'price.number' => '价格必须为数字',
'price.between' => '价格必须在1-100的区间',
'email.email' => '必须符合email格式'
]);
//batch: 显示全部验证错误
$result = $validate->batch(true)->check([
'name' => 'qqq',
'price'=> '122',
'email'=> 'vechis@154.com'
]);
if (!$result) {
dump($validate->getError());
}
}
验证类
可以把所有字段和限制定义到一个数组
统一定义所有字段限制的错误信息
调用的时候简单,还可以指定scene(场景)去使用
同时可以自定义规则
创建
php think make:validate User
编写
文件 app - validate - User.php
填写三个属性
定义验证规则
protected $rule = [
'name|用户名' => 'require|max:20|checkName:xx',
'price|价格' => 'number|between:1,100',
'email|邮箱' => 'email',
'id|序号' => 'number|between:1,33'
];
定义错误信息
protected $message = [
'name.require' => '姓名不得为空',
'name.max' => '姓名不得大于20位',
'price.number' => '价格必须为数字',
'price.between' => '价格必须在1-100的区间',
'email.email' => '必须符合email格式',
'id.number' => '必须为数字!'
];
自定义验证场景
//就是定义key=>要去验证哪些。这样可以在一个类写全部验证的东西,但可以分类哪个去验证哪些
protected $scene = [
'ToVerify' => ['name', 'number'],
'toRoute' => ['id']
];
调用
//调用validate下User的类
//scene: 指定验证哪些
//batch: 开启全部都验证一遍,不开则有一条没验证通过就返回
public function verifyFromData()
{
try {
Validate(User::class)->batch(true)->scene('ToVerify')->check([
'name' => 'ss',
'price'=>'222',
'email'=>'asd@vvv',
'id' =>'dd'
]);
} catch (ValidateException $exception) {
dump($exception->getError());
}
}
路由
自定义url,可以传点参数,可以自己定义中间件/验证器,可以分组管理一个控制器的全部方法。
感觉有点用,但不多的样子。浅谈下。
参数
option: 参数
append: 追加参数
pattern: 正则匹配
cache: 缓存(仅对GET有效,返回304状态码)
ajax: 必须为ajax请求等等。。
domain: 域名
ext: 后缀
filter : 参数过滤
validate : 验证器
middleware: 中间件
Route::rule('aaaaaaaa/:id/:uid', function ($id, $uid) {
echo $id . $uid;
}, 'POST') ->cache(3600)
->ajax()
->option([
'domain' => '10.10.200.27', //域名
'ext' => 'html', //后缀必须为html
'filter' => ['id' => '3', 'uid' => '9'] //传入参数限制
])
->validate([
'id' => 'number|between:1,10',
null,
['id.between' => 'id必须在1到10之间'],
true
])
->middleware(\app\middleware\Auth::class, 'This is Params');
规则
设置全局涉及路由的参数都得这样匹配。 就是在文件前面设个固定匹配。
Route::pattern([
'id' => '\d+',
'uid'=> '\d+'
]);
或单个路由加单个验证 xxx... ->pattern(['id' => '\d+'....]);
基本
在config/route文件配置, 在route/app文件写实际路由。
=》 www.xxx.com/address/details
Route::rule('ad', 'Address/details', 'GET|POST');
=》 www.xxx.com/ad 访问,仅限get/post请求
MISS
匹配不到的时候自动到 Error控制器的Miss方法里
Route::miss('\app\controller\Error@miss');
不需要控制器和方法
闭包支持我们可以通过URL 直接执行,而不需要通过控制器和方法;
Route::rule('xx/:name/:id', function($param) {
rerurn "This Param Use Route To this result" . $patam;
});
跨域
allowCrossDomain
Route::rule('test1' , '\app\controller\Test@Index')->allowCrossDomain([
'Access-Control-Allow-Origin' => 'http://10.10.200.27:666' //闲没事干也可以指定域名访问
]);
分组(*)
Route::group('ad', function (){
Route::rule('gi-:id', 'Address/getId');
Route::rule('gn-:name', 'Address/getName');
Route::rule('gu-:uid', 'Address/reToUid');
})->append(['status' => '1'])
->pattern(['id' => '\d+', 'name' => '\w+'])
-> xxxxx.....;
真实地址
没做路由和做了路由,url傻傻分不清,直接在控制器用url(控制器/方法)->domain(true) 获取真实地址返回前端
public function index()
{
//意思:根据当前控制器返回实际url,tp自动判断经过路由或没经过的最终实际路径。
return url('Url/index')->domain(true);
}
验证器
不建议在路由自己搞验证器
老老实实
->validate(\app\validate\XXX:class)
中间件
不建议在路由自己搞中间件
老老实实
->middleware(\app\middleware\XXX:class)
注解路由
不建议在注释中搞这个
要安装:composer require topthink/think-annotation
再引用:use think\annotation\Route;
/**
-
@param $id
-
@return string
-
@route(“details/:id”);
*/
请求 & 响应
常规简单。
讲一般开发中,前后端交互或者需要后台响应发送过来的请求。
以下什么依赖注入,门面facade, 容器绑定和实例化其实都在做一件事:
为了节约资源,把要调用的类以各种形式实例化。管理类的实例化。
new是新建一个会有开销,依赖注入是从容器取对象,像连接池也是类似
public function __construct(\think\Request $request)
{
$this->request = $request;
}
public function test()
{
//facade
$file1 = Request::param();
//依赖注入
$file2 = $this->request->param();
//容器绑定
$file3 = app('request')->param();
$file1,$file2,$file3完全一样
}
依赖注入
官方: 依赖注入其实本质上是指对类的依赖通过构造器完成自动注入
有人理解: 框架帮我实例化了模型的对象并且作为参数传递进了构造方法这里
我的理解:控制器的构造方法中引入需要的类,指定一个变量接收,就完成了这个类的实例化。
public function __construct(\think\Request $request)
{
$this->request = $request;
}
门面Facade
一种设计模式,为容器的类提供了一种静态的调用方式。
比如请求Request::?,路由Route::?,数据库Db::?等等,均来自Facade;
说下怎么做自己的门面
app -> 新建common目录,-> 新建 TestFacade.php文件 -> 写个简单的方法
namespace app\common;
class TestFacade
{
public function hello($name)
{
return 'Hello !' . $name;
}
}
app -> 新建facade目录 -> 新建同名的TestFacade.php文件 -> 继承门面,引入自写的门面
namespace app\facade;
use think\Facade;
class TestFacade extends Facade
{
protected static function getFacadeClass()
{
//引入facade类,创建静态方法,指向自定义文件夹common里的XXX类
//可在控制器端使用facade方式调用本方法 等同 => common里的XXX类
return 'app\common\TestFacade';
}
}
控制器调用
use app\facade\TestFacade;
dump(TestFacade::hello(1));
容器
bind() , app()
自己建个类,绑个类,app实例化
bind('test', 'app\model\One');
$test = app('test');
HTTPT头
//HTTP头: 是在HTTP请求和响应中传输的元数据,它们提供了关于请求或响应的额外信息,用于控制和管理HTTP通信。
/*
允许客户端和服务器之间交换关键信息,以确保请求和响应的正确处理和解释
1. **Host**:用于指定目标服务器的主机名和端口号。这是必需的,因为同一台服务器上可能托管多个网站。
2. **User-Agent**:包含了发送请求的用户代理(通常是浏览器)的信息。服务器可以使用这个头信息来适应不同的客户端类型。
3. **Accept**:指定客户端接受的内容类型,通常是MIME类型。服务器可以使用这个头信息来确定如何响应请求。
4. **Content-Type**:指定HTTP响应中实际内容的类型。这有助于客户端正确解释服务器返回的数据。
5. **Content-Length**:指定响应正文的长度,以字节为单位。这有助于客户端正确接收和处理响应。
6. **Authorization**:包含身份验证凭据,通常用于向服务器发送用户名和密码,以访问受保护的资源。
7. **Cookie**:包含客户端发送给服务器的cookie数据,用于在多个请求之间保持会话状态。
8. **Set-Cookie**:服务器在响应中设置的cookie信息,用于在客户端上维护会话状态。
9. **Location**:用于重定向客户端到另一个URL。通常在响应码为3xx的响应中使用。
10. **Cache-Control**:用于控制缓存的行为,指定是否可以缓存响应以及缓存的有效期等信息。
11. **Referer**:包含了引导用户到当前页面的URL,通常在跟踪和分析中使用。
12. **User-Agent**:用于识别发送请求的用户代理,有助于服务器适应不同的浏览器和设备。
13. **Date**:指定HTTP消息的创建日期和时间。
14. **Server**:包含服务器的信息,通常是服务器软件的名称和版本。
15. **ETag**:用于支持条件请求,客户端可以使用此标签来检查资源是否已更改。
16. **Accept-Encoding**:指定客户端接受的内容编码方式,服务器可以使用这个头信息来压缩响应内容以提高性能。
17. **Connection**:控制连接的行为,如是否保持持久连接。
*/
Request
请求,就是发送过来的请求, 进行接收。
原生就是 G E T , _GET, GET,_POST[‘xxx’]
现在都用Request, 就是看下Request有啥方法,可以判断请求类型,请求的参数等等
//接收请求的控制器和方法
return Request::controller() . ' | ' . Request::action();
// 匹配指定参数, 没有参数的默认值, 参数匹配的规则
dump(Request::param('name', 'abc', 'htmlspecialchars'));
//匹配全部, 包含route里的字段
Request::param();
//头信息
Request::header();
//请求方法
Request::method();
请求缓存
仅对GET有效,在 app\middleware 文件解除注释
// 全局请求缓存
\think\middleware\CheckRequestCache::class,
在 config/route中, 声明周期,默认为null
// 请求缓存有效期
'request_cache_expire' => 3600,
当第二次访问时,会自动获取请求缓存的数据响应输出,并发送304 状态码;
Response
就是对请求做出回复
return , json(), view()
没啥,就是返回数据而已。
可以在返回的时候加状态码,响应头
return json($data)
->code('201')
->header(['Cache-control' => 'no-cache, must-recalidate'])
重定向
可以访问当前控制器的某个方法A后挑到另外的控制器的某个方法B去。
A跳过去的时候可以记住A的url, 在B的逻辑判断后可以跳回A。
用redirect,还可以在跳的时候设置Session。
public function redirectA()
{
if (Session::get('isRedirect') == 'yes') {
return "已经跳转过了!";
} else {
return redirect(url('Redirect/redirectB'))->with('isRedirect', 'no')->remember();
}
}
public function redirectB()
{
if (Session::has('isRedirect')) {
Session::set('isRedirect', 'yes');
return redirect(Session::get('redirect_url'));
} else {
return "没有东西";
}
}
上传文件
接收前端传入的文件请求,验证文件,保存文件,响应返回文件路径
<form action="Upload/uploadImage" enctype="multipart/form-data" method="post">
<input type="file" name="image" multiple="multiple"><br>
<input type="submit" value="确认">
</form>
响应
public function upload()
{
$validate = Validate::rule([
'image' => 'fileSize:1024000|fileExt:png,gif'
]);
$res = $validate->check([
'image' => Request::file('image') //接收input框中的name值=》image
]);
if (!$res) {
dump($validate->getError());
}
$info = [];
$files = Request::file('image');
foreach ($files as $file) {
$info[] = Filesystem::putFile('topic', $file);
}
dump($info);
}
模板
得安装
composer require topthink/think-view
基础
新建View目录在 app下。 新建同名控制器的目录 Show , 新建同名控制器的方法Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Test SHow</title>
</head>
<body>
hi, {$name} !
<?php
echo $name;
?>
</body>
</html>
新建控制器Show
public function index()
{
View::assign([
'name' => 'xx'
]);
return View::fetch();
}
assign:赋值, fetch赋值
最后输出: Hi, xx xx
配置渲染
渲染其他的
public function useOther()
{
//渲染其他控制器
return View::fetch('Verify/showThis');
//渲染多应用下其他控制器
return View::fetch('admin@User/index');
//渲染公共目录下的文件,要改配置
View::config(['view_dir_name' => 'public']);
return View::fetch('../public/test/index');
}
原生PHP
指定php引擎渲染,这样原本View的html转为php去渲染,都是用php原生语法输出
public function native()
{
return View::engine('php')->fetch('index', [
'name' => 'Mr.xing'
]);
}
原生标签
{php}
if($num1 != $num2){
echo '不相等';
} else {
echo '相等';
}
{/php}
变量输出/运算
public function output()
{
$user = ['name' => 'xing', 'age' => '23', 'gender' => 'man'];
View::assign([
'user' => $user,
'num' => 4,
'num1' => 9,
'flag' => true,
'str' => 'iadoifmgiodbf',
'date' => strtotime('-1 month'),
'obj' => $this
]);
return View::fetch();
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模版的变量输出</title>
</head>
<body>
//数组 <br>
{$user.name}
<hr>
//对象 <br>
{$obj->method}
<hr>
//默认/系统输出 <br>
{$user.salaer|default='未填写工资'}
//自带函数 md5, substr
默认值default, 不使用转义raw, 输出数组的第一个元素first, 输出数组的最后一个元素last, 转换为大写upper <br>
{$date|date='Y-m-d'} <br>
{$str|substr=0,3} <br>
{$user|first} - {$user|last} <br>
{$num|md5} <br>
{$obj->method|raw} <br>
//运算符 +,-,*,/,%,++,-- <br>
{$num1 % $num}
//三元运算符 <br>
{$flag ? '正确' : '错误'}
{$num == $num1 ? '确实相等' : '假的'}
{eq name='$num1' value='$num2'}
<span>相等</span>
{else/}
<p>不相等 = =</p>
{/eq}
</body>
</html>
循环/比较
foreach, for
<table class="table table-bordered table-hover">
{foreach $list as $key => $val}
{eq name='20' value="$val['id']"}
<tr>
<td>{$val['id']}</td>
<td>{$val['username']}</td>
<td>{$val['gender']}</td>
<td>{$val['email']}</td>
<td>{$val['price']}</td>
</tr>
{/eq}
{/foreach}
</table>
{for start = '1' end = '100' step = '10' name = 'todo' }
{$todo}
{/for}
加载
可以设置公共头部,尾部,直接在后续模板中以 include方式引入
还可以指定特别的变量
public function includeFile()
{
View::assign([
'title' => 'includeFile',
'keywords' => 'Test Words!!'
]);
return View::fetch();
}
如公共头部放app/view/public/ header.html 与 foot.html, 特殊字换为[xxx]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>[title]</title>
</head>
<body>
<p>[keywords]</p>
头部
<hr>
在引入CSS,JS文件时,在 config/view文件,指定替换的关键字
// 模版替换输出
'tpl_replace_string' => [
'__JS__' => '/static/js',
'__CSS__' => '/static/css',
]
最后引入的文件就可以简化为:
{include file='public/header' title="$title" keywords="$keywords" }
<link rel="stylesheet" href="__CSS__/base.css">
body.....
<script src="__JS__/base.js"></script>
{include file='public/foot'}
令牌
就是自带的表单验证token,判断表单的内容有没有被篡改
在表单中新增一个input框, 特定name = token value={:token()}
{include file="public/header" title="$title" keywords = "$kw" }
<form action="/verify/token" method="post">
<input type="text" name="username">
<input type="text" name="__token__" value="{:token()}" style="width: 800px">
<input type="submit" value="提交">
</form>
<script src="__JS__/base.js"></script>
{include file="public/foot"}
在控制器端用请求匹配接收和验证
$validate = Validate::rule([
'name' => 'require|token'
]);
$res = $validate->batch(true)->check([
'name' => Request::param('username'),
'__token__' => Request::param('__token__')
]);
if (!$res) {
dump($validate->getError());
}
Cookie/Session/Cache
都大差不差 拿,存,取,删
Cookie
set, has ,get ,delete
public function setCookie()
{
dump(Cookie::set('username', 'name'));
dump(Cookie::has('username'));
dump(Cookie::get('username') . "==" . Request::cookie('username'));
dump(Cookie::delete('username'));
}
Session
set, has, get, delete
flash: 射一次出去了就清空 pull: 拿一次就删
all: 取全部 clear:清全部
Session::set('user', 'x');
dump(Session::has('user'));
dump(Session::get('user'));
dump(Session::delete('user'));
//拿一次就删, 删
Session::pull('user');
//设一次
Session::flash('psw', 'asdvedrg12312313sdv');
//全部取, 全部清
dump(Session::all());
dump(Session::clear());
Cache
tp使用文件缓存
set,has,get, clear
tag: 设置分组标签
Cache::tag('UserCache')->set('age', '30');
Cache::tag('UserCache')->set('sex', '0');
Cache::clear('UserCache');
其他
异常
throw new Exception(…)
HTTP状态码
根据实际错误输出不同HTTP状态码跳转到不同得错误页面
throw new HttpException(404)
在config/app配置
// HTTP 异常页面的模板文件
'http_exception_template' => [
// 定义404错误的重定向页面地址
404 => \think\facade\App::getRootPath() . 'public\404.html',
// 还可以定义其它的HTTP status
401 => \think\facade\App::getRootPath() . 'public\401.html'
],
图像
缩略 文字图片水印 裁剪
得安装: composer require topthink/think-image
public function uploadImage()
{
$upload = Request::file('image');
$file = Filesystem::putFile('image', $upload);
$filepath = Filesystem::getDiskConfig('local', 'root') . '/' . $file;
$image = Image::open($filepath);
// 缩略图
$image->thumb(150, 150, 6)->save($filepath);
// 水印
$image->water('./static/image/logo.png', 9, 50)->save($filepath);
// 文字
$image->text('hello', './static/font/1.ttf', 20, '#ffffff')->save($filepath);
// 裁剪
$image->crop(100, 100, 50, 50)->save($filepath);
}
日志
runtime下得文件形式,真记还得用数据库