# 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 之类

参见 容器和依赖注入 - ThinkPHP 官方手册

接口

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 对象

请求信息

1761465565344

1761465598123

1761465794798

多应用获取当前应用

app('http')->getName();

# 输入变量

以下都使用 facade 静态调用

使用 has 方法检测是否存在

Request::has('id','get');
// 可以检测:
get/post/put/request/cookie/server/session/env/file

获取

1761466068582

比如:

Request::param();// 不加参数表示所有(经过过滤),参数为 false 则是包括过滤掉的,获取部分懒得说了

注意:路由变量不是使用 get 方法获取,而是 param ,支持使用第二个参数作为默认值,以及使用第三个参数组为过滤规则,如: 'htmlspecialchars' , 'strip_tags,strtolower' , org\Filter::safeHtml

获取部分变量也可以:

Request::only(['id','name']);
// 可以传第二个参数作为请求方法,第一个若是字典则值为默认值

变量修饰符

1761471554925

中间件变量不会改变 param 获取到的值

为了简化,可以使用助手函数:

1761471680305

1761471701068

判断请求类型:

1761471753042

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

1761471823409

此时获取原始类型: 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');

省略伪静态 / 参数绑定 / 请求缓存

# 响应

1761474448187

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

请求头也可以用一些快捷的:
1761474908673

1761474923330

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

1761475779091

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 注入

其他见 PHP: 预定义常量 - Manual

配置文件的 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') 省略

也许常用的链式操作:

1761735230109

1761735247346

1761735261837

->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]);

# 事件

1761736380823

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 都由上面的属性给出
    }
}

1761827988818

默认获取的表名都是小写字母,如果有大写的必须指定 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 查询时不生效,模型事件只对模型生效
有三种实现方式:

1762050953325

这些回调方法都支持传入当前模型对象实例,以及通过依赖注入增加额外参数

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(...)

# 模板渲染

路径:

1762089676952

或者视图单独出来

1762089729832

如果 flask 用习惯了(???)配置参数:

'view_dir_name'=>'templates'

动态的:

public function index(){
	View::config(['view_path'=>'mypath']);
    return View::fetch()
}

注意

1762089868931

如果不想要:

'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)

剩余部分滚回去看官方文档