# OPEN
# 杂项
namespace app\controller; // 声明控制器的命名空间,对应于实际的文件夹结构 | |
use think\Request; // 将其他命名空间的类拉到当前空间 | |
前者是因为不同模块可能有重名的控制器,比如index控制器 | |
后者只是纯导入要用到的 |
# 依赖注入
需要添加对象类型约束,不限制数量和顺序
对于框架原生支持的情景,无需额外配置。比如在控制器中:
public function getUser(Request $request, UserService $userService, $id) | |
{ | |
$userId = $request->param('id', $id); | |
return $userService->getUserInfo($userId); | |
} |
在路由闭包,事件类 / 中间件的执行方法,也可以注入:
// 路由定义:访问 /test 时自动注入 Request 实例,这里也能发现函数本身也是可以的 | |
Route::get('test', function(Request $request) { | |
return $request->url(); | |
}); | |
// 事件类和中间件需要使用过 handle 方法: | |
class UserLogin | |
{ | |
public function handle($user, Request $request) | |
{ | |
$user->login_ip = $request->ip(); | |
} | |
} | |
class CheckLogin | |
{ | |
public function handle($request, \Closure $next, Request $req) | |
{ | |
...// 业务逻辑 | |
return $next($request); | |
} | |
} |
如果要注入的是自定义的类,需要使用助手函数 invoke () 来触发注入:
class Bar {} // 依赖类 | |
class Foo { | |
public function __construct(Bar $bar) {} | |
} | |
$foo=invoke('Foo'); | |
// 相当于 $bar=new Bar ();$foo=new Foo ($bar) | |
// 如果是对其中的方法注入: | |
class Foo{ | |
public fuction bar(Bar $bar) | |
} | |
$foo=invoke(['Foo','bar']); |
此外,全局函数或闭包 (匿名函数) 都是可以使用 invoke 依赖注入的
$result=invoke(fuction(Bar $bar){...}) |
如果有其他要转入的普通参数,必须先手动创建 Bar 实例,再注入
class Bar{ | |
protect $key; | |
public function __construct($key){ | |
$this->key=$key; | |
} | |
} | |
...其余不变 | |
$bar=new Bar('my_key'); | |
$foo=new Foo($bar) / $foo=invoke('Foo',['bar'=> $bar]); |
如果是配置项等读取来的固定值,可以使用容器绑定来支持普通的依赖注入:
$bar= new Bar('my_key'); | |
Container::set('Bar',$bar);// 把实例存入与类名相同的容器 | |
// 由下面的容器绑定可知,相当于 bind ('Bar',$bar); | |
$foo=invoke('Foo'); // 直接注入 |
# 容器绑定
通过容器提前注册依赖
绑定类标识,也就是创建别名
// 通过容器对象 | |
$this->app->bind("think\Cache"."app\common\Cache"); | |
// 给自定义的 Cache 绑定到原 think\Cache 标识 | |
// 通过助手函数 bind | |
bind('cache','think\Cache');// 用 cache 标识 think、Cache |
也可以绑定闭包
bind('sayhello',fuction($name){return 'hello'.$name}); | |
随后使用: | |
$result=app('sayhello',['name'=>'ThinkPHP']); |
绑定实例
便于复用一些实例
$customCache = new think\Cache([ | |
'type' => 'redis', | |
'host' => '127.0.0.1' | |
]); | |
bind('cache', $customCache); | |
// 后续调用 | |
$cache = app('cache'); |
bind 只能绑定该类的对象
存在内置的容器绑定标识
例如 thinkphp\App => app 之类
接口
bind('think\LoggerInterface',"think\Log"); | |
//think\LoggerInterface 接口使用 think\Log 实现 | |
use think\LoggerInterface | |
这样的好处是,如果此后要更改实现函数,可以只修改bind。代码依赖抽象接口而非具体类 |
可以批量绑定
// 在 app/provider.php 定义,这个配置是全局而非单个应用的 | |
return ['route' => \think\Route:class,'session'=> \think\Route\Session::class] | |
// 然后就可以直接使用 | |
$route = app('route'); |
容器是会自动缓存的,当我们使用依赖注入或者 app 调用时,都会创建缓存,但是我们可能需要创建该类的另一个实例
$cache1 = app('cache'); | |
$cache2 = app('cache'); | |
//$cache1===$cache2 | |
$cache3=app('cache', [], true); | |
// 强制使用新的实例,[] 是参数,ture 表示不使用缓存。注意这只是单例,不一定覆盖缓存。 | |
//$cache1!==cache3 |
如果手动绑定实例(比如使用 bind),由于其是绑定固定实例而非创建对象实例,无法通过上面的操作重新实例化
并非一定要绑定
$cache = app('org\utils\ArraryItem'); |
# 对象化调用
app () 这个助手函数用于获取容器中的标识,并将其像对象一样调用,本质是对容器语法的封装,比如:
app()->cache;app()->cache=xxx | |
分别等价于 | |
app()->get('cache');app()->bind('cache', ...) | |
// 可以 $app=app ();$app->cache |
$app = app(); | |
isset($app->cache); | |
$cach=app->cache; | |
// 可以使用依赖注入 | |
app()->session->get('user_name'); |
# 自动注入 / 自定义实例化
自动注入基于依赖注入,可以检查容器中是否已经有对象实例,没有则自动实例化
Route::get('User/:id','index/Index/hello')->model('\app\index\model\User') | |
// 匹配路由规则,当用户访问 User/:id 时,自动获取:id 参数,并向 index/Index/hello 方法注入使用:id 创建的 '\app\index\model\User' 实例 | |
class Index | |
{ | |
public function hello(User $user) | |
{ | |
return 'Hello,'.$user->name; | |
} | |
} |
自定义实例化需要在对象中增加 __make 静态方法,此时 new User 将使用该方法而不是默认的
use think\Model; | |
use think\db\Query; | |
class User extend Model | |
{ | |
public static fuction __make(Query $query){ | |
return (new self()->setQuery($query)); | |
} | |
} |
# 回调机制
允许对象被容器实例化后,触发一段特定的代码以实现定制
Container::getInstance()->resolving(fuction($instacne,$container){ | |
//$instance 刚创建的对象实例 | |
//$container 容器本身 | |
... | |
}) | |
// 也可以针对某个类 | |
Container::getInstance()->resolving(\think\Cache::class,fuction($instacne,$container){...}) | |
//\think\Cache::class 等价于 \think\Cache,前者会把类型标识符转换为字符串 |
# 路由
use think\facade\Route; | |
Route::rule('new/:id','News/read',"GET|POST"); | |
// 将 /new/<xxx > 注册到 News 控制器下的 read 操作,即路由到 News/read/id/<xxx> | |
也可以 | |
Route::get(...); | |
Route::post(...); | |
// 所有方法都有这样的用法 |
规则表达式:
Route::rule('/','/index'); | |
Route::rule('my',"Member/myinfo"); | |
Route::rule('blog/:id$',"Blog/read");//$ 标识完全匹配 | |
Route::rule('new/:year/:month/:day','Blog/read') |
变量写成 <xxx> 也是可以的
可选变量:[:xxx]
额外参数:Route::get ('blog/:id','blog/read')->append (['statue' => 1,'app_id' => 5]);
可选参数:Route::get ('new/:name$', 'News/read')->option ('rule','admin');
或者
Route::get('new/:name$', 'News/read')->rule('admin');
路由标识
用于快速生成地址
Route::rule('new/:id','News/read')->name('new_read'); | |
url('new_read',['id'=>10]); | |
等价于: | |
url('News/read',['id'=>10]); |
路由到控制器 / 操作
Route::get('blog/:id','Blog/read'); | |
//对应的: | |
<?php | |
namespace app\index\controller; | |
class Blog{ | |
public fuction read($id) | |
{ | |
return 'read'.$id; | |
} | |
} | |
?> |
可以多级控制器
Route::get('blog/:id','group.Blog/read'); | |
对应实际路径: | |
app/controller/group/Blog.php | |
命名空间: | |
app\index\controller\group\Blog | |
调用其read方法,不同目录下可以有同名的控制器 |
到类
当要使用的方法不在控制器中,到路径后用 @(动态方法)或者::(静态方法)调用方法
Route::get('blog/:id','\app\index\service\Blog@read'); |
到模板
Route::view('blog/:id','index/hello',[额外变量]); |
到闭包
Route::rule('hello/:name',fuction(Request $request,$name){...}) |
Route::get('blog/:id',\app\route\BlogDispatch::class); | |
// 对应的: | |
namespace app\route; | |
use think\route\Dispatch; | |
class BlogDispatch extends Dispatch | |
{ | |
public function exec() | |
{ | |
// 自定义路由调度 | |
} | |
} |
重定向路由
Route::redirect('blog/:id','xxxx',302) |
路由参数
使用特定的方法来对参数进行一定的检查
Route::get('new/:id','News/read')->ext('html')->https(); | |
#检测是否时 html 结尾,以及是否为 https,等价于: | |
Route::get('new/:id','News/read')->option(['ext'=>'html','https'=>true]); |
URL 后缀可以是:false 禁伪静态访问,空字符串允许任意伪静态后缀,xxxx 只允许特定的,如有多个 | 连接,如
html|htm
可能用到的方法:
domain
->domain('news.thinkphp.cn') // 完整域名检测,也可以只是子域名,如 news |
ajax/pjax/json
->json(); |
filter / match
->filter(fuction($request){xxx}); | |
->filter(\app\common\filter\NewsFilter::class . '@check'); | |
//filter 默认只是预处理,不影响是否继续执行,除非其中 return; | |
//match 通过返回的 bool 值决定是否执行 |
model
模型绑定,用参数置返回对象
Route::get('hello/:id', 'index/hello')->model('id','\app\index\model\User',false); | |
// 路由主键也是 id 时,第一个可以省略,第三个可选选项,false 表示没找到也不抛出异常,否则框架会直接抛 404 | |
// 多个参数使用 & amp; 连接 |
也可以绑定闭包返回需要的模型对象
header
->header(['Content-Type'=>'application/json']); |
路由中间件
Route::rule('hello/:name','hello')->middleware(\app\middleware\Auth::class,"admin"); | |
// 可以对路由分组 | |
Route::group('blog',fuction(){ | |
Route::get(...); | |
Route::put(...); | |
... | |
Route::post(...)->withoutMiddleware(); | |
})->middleware(...)->auto(); | |
// 自动 URL 调度 |
两者的参数都可以为数组,额外参数同理
全局配置在 config/route.php
'middleware'=>[...] |
接下来的内容会更为简略,只解释作用而不是用法:
路由分组
使用上面的 group 实现,可以使用 prefix,ext 甚至是 auto 等,分组可以嵌套,子分组会继承父分组的参数与规则,但是最终以路由规则中为最优先
比如:
Route::group('blog',fuction(){ | |
Route::get(":id",'blog/read'); | |
Route::post("id","blog/update"); | |
})->ext(html)->pattern(["id"=>"\d+"]); | |
// 等同于 | |
Route::group('blog',fuction(){ | |
Route::get(":id",'read'); | |
Route::post("id","update"); | |
})->prefix('blog/')->ext(html)->pattern(["id"=>"\d+"]); |
完全匹配使用 ->completeMatch()
分组绑定可以是命名空间 ->namespace() ,控制器 ->controller() 或者某个类 class(),可以是服务或者工具类
请求/blog/article/detail/100自动调用app\controller\blog\ArticleController@detail(100) | |
// 可以看一下 url 和调用之间的关系!有命名约定 | |
请求/blog/statistic自动调用Blog::statistic() |
默认的 URL 调度规则
https://domainName/groupName.../controllerName/actionName | |
(...是因为存在多级分组) |
资源路由
Route::resource('blog','Blog'); | |
资源路由会自带一个同路由名的参数,使用->vars(['blog'=>'blog_id'])来变更其名 |
资源路由本身遵循 RETfull 规范,资源优先,操作在后,实际的 url 是 "/blog/{blog}/edit"
即 /{资源名}/{资源标识}/{操作},可以限定执行
->only(['index','read','edit']);// 白 | |
->except(['index','delete'])// 黑 |
可以嵌套:
Route::resource('blog', 'Blog'); | |
Route::resource('blog.comment','Comment'); | |
// 路由规则 | |
blog/:blog_id/comment/:id | |
blog/:blog_id/comment/:id/edit | |
// 注意这里只是表面 comment 是 blog 子资源,并没有调用 BlogController |
域名路由
为特定的域名或者子域名注册独立的路由规则
Route::domain('blog',fuction(){ | |
Route::rule("new/:id","new/read"); | |
Route::rule("user","user/info"); | |
}); | |
// 访问 blog.xxx.com/new/123, 映射到 news 控制器的 read 方法,id=123,第二个类似,防止路由冲突,多子域名传入数组,如 ['blog','admin'] |
如果要在所有域名生效:
Route::group(fuction(){ | |
... | |
})->crossDomainRule(); |
但是这个优先级低于专属规则
Route::domain('blog'.fuction(){ | |
... | |
})->layer('blog'); | |
/*layer 会为当前域名的所有路由附加一个层级,正常是 app/controller/xxx | |
加上后是 app/blog/controller/xxx,相应的命名空间也从 app/controller 变为 app/blog/controller*/ |
可以直接绑定到响应对象,无须控制器
Route::domain('test',response("NOT FOUND")->code(404)); | |
// 直接返回的内容可以直接就是 html | |
Route::domain('test',json(['code'=>404,"msg"=> "NOT FOUND","data"=>null])->code(404));// 还是要手动设置状态码 |
miss 路由
为没有匹配到的执行
Route::miss('public/miss');// 指定 public 控制器的 miss 方法处理 | |
Route::miss(fuction(){return "xxx";});// 直接用闭包处理 | |
Route::miss('public/miss', 'get');// 对于未匹配到的 get 请求 |
也可以为分组或者域名设置其单独的 miss 路由
Route::group('blog',fuction(){ | |
Route::miss('blog/miss'); | |
}); | |
// 域名同理 |
这里省略注解路由 / URL 生成(人型生成机?)/ 跨域请求
# 控制器
分别对应单 / 多应用模式
<?php | |
namespace app\[shop\]controller | |
class User{ | |
public function login(){ | |
return 'login'; | |
} | |
} | |
?> | |
//实际文件在app\[shop\]controller\User.php,多应用在URL中也会体现出来 |
渲染输出
<?php | |
namespace app\index\controller; | |
class Index{ | |
public function hello(){ | |
return 'hello,world'; | |
} | |
} | |
?> |
还可以输出:
json($data); | |
view();// 使用模板 |
不要控制器中使用 die 之类的中断,使用 halt ('xxx') 调试
多级控制器类似使用,但必须在路由定义之后
Route::get("user/blog","user.blog/index"); |
空控制器
当找不到对应控制器时执行,实际上就是利用__call 魔术方法
namespace app\controller | |
class Error{ | |
public function __call($method,$args) | |
{ | |
return "xxx"; | |
} | |
} | |
// 单应用模式,两个参数时强制传递的,便于得知出现了什么不存在的输入 |
资源控制器
Route::resource('blog', 'Blog'); | |
// 注册资源路由,用法见上 |
需要先通过命令行生成:
php think make:controller Blog
也可以用完整的命名空间:Blog 改成 app\controller\Blog接口开发可以后跟 --api
# 控制器中间件
只需在控制器中定义 middleware 属性
namespace app\controller; | |
use app\middleware\Auth; | |
class Index{ | |
protected $middleware=[Auth::class]; | |
public function index(){...} | |
public function hello(){...} | |
} |
可以设置生效操作
protected $middleware=[Auth::class.':admin'=>['except'=>['hello']],'Hello'=>['only'=>'hello'],]; | |
// 后一个参数表示生效范围,前一个表示的是使用的方法,注意这里的 Auth::class 等和 'Hello' 等是并列的两个中间件,只是后者是别名并且无需传参 |
中间件可以返回参数:
namespace app\http\middleware; | |
class Hello{ | |
public function handle($request,\Closure $next){ | |
$request->hello='xxx'; | |
return $next($request); | |
} | |
} |
然后就可以在控制器中直接使用:
public function index(Request $request){ | |
return $request->hello; | |
} |
# 请求
可以构造方法注入 protected $request 或者对每个方法依赖注入(操作方法注入)
如果就是不想用哪些,也可以用 facade 静态调用,这样就无需任何传递 Request::param('name') 。如果连 use\facade\Request 都不想写,还可以使用助手函数 request()->param('name') ;
! 能发现光是获取 Request 对象都有 4 种方法了!
省略自定义 Request 对象
请求信息



多应用获取当前应用
app('http')->getName(); |
# 输入变量
以下都使用 facade 静态调用
使用 has 方法检测是否存在
Request::has('id','get'); | |
// 可以检测: | |
get/post/put/request/cookie/server/session/env/file |
获取

比如:
Request::param();// 不加参数表示所有(经过过滤),参数为 false 则是包括过滤掉的,获取部分懒得说了 |
注意:路由变量不是使用 get 方法获取,而是 param ,支持使用第二个参数作为默认值,以及使用第三个参数组为过滤规则,如: 'htmlspecialchars' , 'strip_tags,strtolower' , org\Filter::safeHtml
获取部分变量也可以:
Request::only(['id','name']); | |
// 可以传第二个参数作为请求方法,第一个若是字典则值为默认值 |
变量修饰符

中间件变量不会改变 param 获取到的值
为了简化,可以使用助手函数:


判断请求类型:

支持请求类型伪装(从其他类型伪装成 POST):

此时获取原始类型: Request::method(true)
任何类型能伪装成 pjax/ajax:
GET: ?_ajax=1 / ?_pjax=1 ,POST 类上
请求头
$info = Request::header(); | |
echo $info['accept']; | |
echo $info['accept-encoding']; | |
echo $info['user-agent']; | |
// 如果只要其中一个 | |
Request::header('user-agent'); |
省略伪静态 / 参数绑定 / 请求缓存
# 响应

namespace app\controller; | |
class Index | |
{ | |
public function hello() | |
{ | |
$data = ['name' => 'thinkphp', 'status' => '1']; | |
return json($data); | |
} | |
} |
也可以直接 return 字符串
响应参数
->code(404); | |
->data($data);// 设置原始数据,但是会根据前面的转换,并不一定是实际输出 | |
->header(['Cache-control'=>'no-cache,must-revaildate']); | |
->redirect();// 站内可以直接 /xxx,也可以用 (string) url () 自动生成 | |
->download('源文件路径,相对时通常是对应public/xxx','下载时显示');// 路径是服务器路径,不是 url, 可以结合 ->empire () 设置有效期。第二个参数可选。 |
很多时候 code 可以合并到前面
->json($data,404); |
请求头也可以用一些快捷的:


用到额外参数应使用 options 处理,比如 jsonp 有时需要设置 jsonp_handler 等参数。

PS:name 会覆盖原来的,force 是当你需要打开而不是下载时使用,值为 false
也可以直接把已知的内容写到文件里供下载
$data="FLAG"; | |
return download($data,'flag.txt',true)// 第三个参数为 true 来指定这种用法 |
# 数据库
在配置文件 database.php 中:
return [ | |
'default' => 'mysql', | |
'connections' => [ | |
'mysql' => [ | |
// 数据库类型 | |
'type' => 'mysql', | |
// 服务器地址 | |
'hostname' => '127.0.0.1', | |
// 数据库名 | |
'database' => 'thinkphp', | |
// 数据库用户名 | |
'username' => 'root', | |
// 数据库密码 | |
'password' => '', | |
// 数据库连接端口 | |
'hostport' => '', | |
// 数据库连接参数 | |
'params' => [], | |
// 数据库编码默认采用 utf8 | |
'charset' => 'utf8', | |
// 数据库表前缀 | |
'prefix' => 'think_', | |
// 重连 | |
'break_reconnect' => true, | |
// 如果不能自动识别断线错误 | |
'break_match_str' => [ | |
'error with', | |
], | |
], | |
'potato' => [ | |
'type' =>'potato_database', | |
... | |
], | |
], | |
]; |
连接参数:
PDO::ATTR_CASE => PDO::CASE_NATURAL, | |
// 控制字段大小写,此外还有 PDO::CASE_LOWER 和 PDO::CASE_UPPER | |
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, | |
// 处理错误,这里是抛出异常,此外还有 PDO::ERRMODE_SILENT,PDO::WARNING(只设置错误代码。警告) | |
PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, | |
// 只针对 ORACLE 数据库,对于空值的处理,PDO::NULL_TO_STRING,PDO::NULL_EMPTY_STRING | |
PDO::ATTR_STRINGIFY_FETCHES => false, | |
//true 时将所有数值类型都转换为字符串 | |
PDO::ATTR_EMULATE_PREPARES => false, | |
//true 时将使用 PDO 模拟预处理,可能导致 SQL 注入 |
配置文件的 params 会和内置的合并
切换连接
Db::connect('potato')->table('user')->where('id',10)->find() |
连接是惰性的,实例化时不连接数据库,只有有操作时才会连接
关闭连接
Db::connect()->close(); |
省略分布式数据库以及读写分离
# 操作
无论何时,记得先引入门面类
# 查询
use think\facade\Db; | |
Db::table("potato")->where('id',1)->find/findOrEmpty/findOrfail(); | |
//mysql:SELECT * FROM 'potato' WHERE "id" = 1 LIMIT 1, 注意限制一行,后两个分贝是没找到返回空数组,以及报错(而不是 find 的 null) | |
Db::table("potato")->where('status',1)->select/selectOrFail()->toArray();// 这个是返回对象 | |
// 如果设置了前缀,使用 name 代替 table,否则都可以 | |
->value('name');// 查询某个字段的值 | |
->column('name','id');// 查找字段,可选的第二个参数作为索引,返回数组,查询列 | |
显然; | |
->column('*','id'); // 不存在返回空 | |
->lazyFields()->;// 不使用缓存,实时读取 |
分批处理:
Db::table('potato')->...->chunk(100,function($users){ | |
foreach ($users as $user){ | |
// 一次处理 100 个用户表数据,其中 return false 终止后续处理 | |
} | |
},<'create_time'>,<'desc'>);// 不指定按主键 | |
// 也可以使用 cursor (),省略 |
# 添加
Db::name('user')->insert/save($data);//$data 为字典数组,save 会自动处理是 update 还是 insert,mysql 的 insert () 前加 replace () 类似 | |
->insertGetId($data);// 插入并返回自增主键,而不是插入成功的数目 | |
->strict(false)->insert($data);// 不存在就抛弃,而不是抛异常 | |
->insertAll($data);// 将这个字典数组的数组中的全部插入,返回成功数 | |
可以使用->limit(100)->insertAll($data);分批 |
# 更新
差不多
->exp('name','UPPER(name)')// 对参数先用 SQL 函数处理,是 UPDATE ... SET=UPPER (name)... | |
也可以使用raw(): | |
->update([ | |
'name' => Db::raw('UPPER(name)'), | |
'score' => Db::raw('score-3'), | |
'read_time' => Db::raw('read_time+1') | |
]) | |
传参也能用->exp($data)->,优先级不如自身传参 |
自增 / 减
->dec/inc('score', 5)->// 第二个参数默认 1,单次更新可以 setInc/setDec, 此时无需 update,并且第三个参数延长更新(s) |
# 删除
自己看
// 根据主键删除 | |
Db::table('think_user')->delete(1); | |
Db::table('think_user')->delete([1,2,3]); | |
// 条件删除 | |
Db::table('think_user')->where('id',1)->delete(); | |
Db::table('think_user')->where('id','<',10)->delete(); | |
Db::name('user')->delete(true);// 跑路 |
有一些特殊的如 Db::name('user')->where('name', 'like', 'thinkphp%')->select(); ,以及 where[Not]Null('name') 省略
也许常用的链式操作:



->union('SELECT name FROM think_user_2')->select()// 可以多个联合 | |
->getOptions()// 查询生成的语句条件(json 格式) |
对于特定操作有内置用法,如 ->max/min/avg/sum (),省去 GROUP 中单独调用这些
分页
$list = Db::name('user')->where('status',1)->order('id', 'desc')->paginate(10);// 一次显示 10 条,可以加第二个参数作为上限 |
诸如时间查询,高级查询等省略。但是下面这个不能不提!
Db::query("select * from think_user where status=:id", ['id' => 1]); |
# 事件

Db::event('before_select',function ($query){...}) |
# 模型
# 定义
namespace app\Model; | |
use think\Model; | |
class User extends Model | |
{ | |
protected $name='User'; | |
protected $table='User'; | |
protected static function init(){ | |
// 初始化,该方法只在第一次实例化时运行 | |
User::where('id','>',10)->select(); | |
// 等价于 Db::name ('user')->where ('id','>',10)->select (); | |
// 此时的 name 和 table 都由上面的属性给出 | |
} | |
} |

默认获取的表名都是小写字母,如果有大写的必须指定 table,并且 table 的优先级大于 name(不指定前缀时)这也是为什么上面等价的式子中写的是 user---- 如果没有 name 和 table 的话!
后缀是可以动态的
查询: Blog::suffix('_en')->find();
设置: Blog::setSuffix('_en')->save();
模型的方法可以使用 invoke 一个闭包来实现依赖注入(这不是废话?)
public function getTestFieldAttr($value,$data){ | |
return $this->invoke(function(Request $request) use($value,$data){return $data['name'].request->action();}) | |
} | |
//invoke 的第二个参数用于传参,多个需要改成数组 |
# 字段
protected $schema = [ | |
'id' => 'int', | |
'name' => 'string', | |
'status' => 'int', | |
'score' => 'float', | |
'create_time' => 'datetime', | |
'update_time' => 'datetime', | |
]; | |
// 实际上会自动获取,这样只是省去自动查询,部署后可以通过命令自己生成: | |
php think optimize:schema |
获取数据:
$user=User::find(1); | |
echo $user->create_time; | |
echo $user->name; | |
等价于; | |
echo $user['create_time']; | |
echo $user['name']; | |
// 懒得解释 |
但是查询数据建议在控制器中写,而不是模型,并且在模型中最好用:
$user->getAttr('create_name'); |
# 使用
$user=new User(); | |
$user->name='thinkphp'; |
如果定义了修改器(比如 setScoreAddr 在对 score 属性赋值时自动触发,一般用于对数据预先处理),而不想触发它,可以在定义时传入构造函数。对于已经定义的对象:
$user = new User(); | |
$data['name'] = 'thinkphp'; | |
$data['score'] = 100; | |
$user->data($data); | |
// 当存在第二个参数且为 true 时调用修改器,此时只是批量赋值,第三个参数是数组,作为过滤器,表示只设置其中的几个,忽略其他。注意如果没有第三个参数就是整体覆盖,没有在 $data 中重新赋值的部分数据会被清除 |
可以给这个对象新增属性:
$user = new User(); | |
$user->group_id = 1; | |
$data['name'] = 'thinkphp'; | |
$data['score'] = 100; | |
$user->appendData($data); // 如果调用 data , 则清空 group_id 字段数据 | |
// 如果是已有的属性,会覆盖对应的部分,同样可以指定第二个参数 true |
其他有的没的省略
所有赋值完成后必须保存:
$user->save(); | |
// 也可以直接对 save 传入字典来批量赋值。还可以传入对象实例,只写入已有字段。可以指定字段: | |
$user->allowField(['name','email'])->save($_POST); | |
这种情景下可以等价为; | |
$data=Request::only(['name','email']); | |
$user->save($data);//save 新增数据会返回布尔值 |
自动类型转换
namespace app\model; | |
use think\Model; | |
class User extends Model{ | |
protected $type = [ | |
'status' => 'integer', | |
'score' => 'float', | |
'birthday' => 'datatime', | |
'info' => 'array' | |
] | |
} |
写入和输出的时候都是自动转换
可以使用特定的格式存储:
class User extends Model | |
{ | |
protected $dataFormat = 'Y/m/d'; | |
protected $type = [ | |
'birthday' => 'timestamp', | |
]; | |
// 此时如果调用 User::find (1)->birthday; 输出 2025/11/1 |
输出到 view
如果在视图中:
<p>你是:{$user->full_name}</p> |
可以:
public function getFullNameAttr(){ | |
$user=User::find(1); | |
View::assign('user',$user);// 将此处的 $user 变量赋值给视图中的 | |
return View::fetch(); | |
} |
模型事件
使用 Db 查询时不生效,模型事件只对模型生效
有三种实现方式:

这些回调方法都支持传入当前模型对象实例,以及通过依赖注入增加额外参数
EVENT::listen('app\model\User.BeforeUpdate',function($user){}); | |
EVENT::listen('app\model\User.AfterDelete',function($user){}); |
before 类型的事件方方法 return false 或者报错,不会进行后续操作
除了使用 Event,也可以直接在模型中定义相关事件的响应:
class User extends Model | |
{ | |
public static fuction onBeforeUpdate($user){ | |
return false | |
} | |
} |
同样可以依赖注入,如果想要某个情况下不适用 Event:
$user->withEvent(false)->save() |
如果只是为了回调,还可以使用观察者:
namespace app\observer; | |
use app\model\User; | |
class UserObserver{ | |
public function onBeforeUpdate(User $user){...} | |
} | |
// 注意这里的 user 是怎么来的 |
在模型中设置:
protected $eventObserver = UserObserver::class |
关联模型(属性)
thinkphp 的关联属性是通过方法实现的
$user=User::find(1); | |
$user->profile; | |
$user->profile->moblie; | |
// 实际的定义可能是: | |
class User extends Model{ | |
$name="potato" | |
public function profile(){ | |
return $this->hasOne(Profile::class); | |
//hasOne 方法用户返回关联对象,即 think\model\relation\HasOne 实例 | |
// 关联字段默认是 id, 也就是说 Profile 的 user_id 对应 User 表的 id,可以是使用第二个参数指定 | |
} | |
} | |
class Profile extends Model{ | |
$moblie=123456789; | |
} |
->belongsTo(User::class) | |
$profile->user->name; |
暂时省略其他关联方式以及主键外键之类的,遇到再说
# 虚拟模型
数据不写入数据库,而是存储在内容中的模型 (不能使用查询语句,只能使用实例),没有实际对应的表。只能关联一般模型,但是不能被反向关联(实例可以直接使用,也没有任何必要关联)
use think\model\concern\Virtual; | |
class User extends Model{ | |
use Virtual; | |
public function blog(){ | |
return $this->hasMany('Blog','user_id'); | |
// 关联普通模型 Blog 的 blog 数据表,依赖 user_id 字段 | |
} | |
} | |
// 直接使用实例 | |
$data = ['name' => 'potatowo']; | |
$user=User::create($data); | |
$user->name="potato"; | |
$user->save();// 这里即使 save,属性也会改变,但是不会触发事件 | |
$user->blog/blog()->where("create_time",">","2025-11-2")->select(); | |
//blog () 关联方法到 blog 关联属性的转换会将驼峰变成_+ 小写,比如 userProfile () 对应 user_profile | |
$user->profile->save(['email'=>'potato']); | |
$blog->together(['content'=>['titile','content']])->save() | |
// 关联保存 |
省略预载入查询,相对关联,以及实体模型,ORM 的部分
# 视图
模板赋值
这个在控制器中提到过:
View::assign("name","POTATOWO");// 批量使用数组 | |
return View::fetch('index',['name'=>'potatowo']);// 模板输出 | |
return view(同fetch);// 使用助手函数 | |
//assgin 方法是全局赋值,fetch/view 中则是单次 | |
return View::display($content,[...])// 直接渲染对应内容 |
使用助手函数
过滤
View::filter(function($content){ | |
return str_replace("\r\n",'<br/>',$content); | |
})->fetch(); | |
或者; | |
view()->filter(...) |
# 模板渲染
路径:

或者视图单独出来

如果 flask 用习惯了(???)配置参数:
'view_dir_name'=>'templates' |
动态的:
public function index(){ | |
View::config(['view_path'=>'mypath']); | |
return View::fetch() | |
} |
注意

如果不想要:
'view_depr'=>'3'//1 是默认的,2 是直接全部转小写,3 不变,也可以指定字符,比如_, 会使得 member/read 变成 member_read |
不按定义:
fetch('member/read');// 访问其他控制器下的模板 | |
fetch(admin@member/edit);// 访问其他应用或者模块的模板 | |
fetch('/menu');// 直接从视图根目录开始读 | |
fetch('../template/public/menu.tpl')// 相对于当前项目入口的位置(即 public/xxx.php) |
剩余部分滚回去看官方文档