第5章 TP5.0 控制器

本文深入解析了ThinkPHP5框架中的路由设置与控制器使用技巧,包括路由优化、模式切换、批量设置及控制器初始化等关键知识点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上次复习
1、路由

a、有利于SEO优化

b、简洁方便记忆

2、路由模式

1、普通模式(PATHINFO)
	http://www.tp.com/index.php/Index/Index/index

2、混合模式
	支持PATHINFO和路由

3、强制模式
	只支持路由模式

4、模式切换和配置文件有关
	// 是否开启路由
	'url_route_on'           => true,
	// 是否强制使用路由
	'url_route_must'         => false,

3、设置路由

0、配置文件
	C:\AppServ\www\tp5\application\Route.php

1、动态设置当个路由
	use think\Route;

	Route::rule();
	Route::get();
	Route::post();
	Route::delete();
	Route::put();

2、动态批量设置路由
	Route::rule([
		'路由规则1'=>'路由地址和参数',
		'路由规则2'=>['路由地址和参数','匹配参数(数组)','变量规则(数组)']
		...
	],'','请求类型','匹配参数(数组)','变量规则');

3、配置文件注册路由
	return[
		'new/:id' => 'News/read',
		'blog/:id' => ['Blog/update',['method'=>'post|put'],['id'=>'\d+']],
	];

==============================================
今日学习

1、新建控制器(前台模块为例)

1、在前台控制器目录下(C:\AppServ\www\tp5\application\index\controller) 新建控制器文件 User.php

2、User.php文件中如何书写
	<?php 
	// 声明命名空间
	namespace app\index\controller;
	// 声明控制器
	class User{
		// 声明方法
		public function index(){
			return "我是User控制器下的index方法";
		}
	}

3、地址栏访问
	http://www.tp.com/index.php/Index/user/index

4、注意:
	1、控制器文件名必须首字母大写,文件名采用驼峰命名。

	2、控制器名必须跟文件名一一对应

	3、命名空间必须和文件名对应
		namespace app\index\controller;
		C:\AppServ\www\tp5\application\index\controller

	4、如果控制器名字采用驼峰命名 UserInfo.php

		http://www.tp.com/index.php/Index/user_info/index

2、控制器如何加载页面

1、系统View类

	$view=new \think\View;
	return $view->fetch();

	use think\View;
	$view=new View();
	return $view->fetch();

2、系统Controller类
	1、需要继承系统控制器类

		use think\Controller;
		// 声明控制器
		class User extends Controller
	
	2、直接使用系统控制器类的方法
		return $this->fetch();

3、系统函数

	return view();

3、数据输出

1、在应用配置文件中可以设置数据返回格式

	'default_return_type'    => 'json', #默认是html

2、ajax请求的时候如何返回数据

	'default_ajax_return'    => 'json',

4、控制器的初始化

1、控制器初始化方法必须继承系统控制器

	// 控制器的初始化方法
	public function _initialize(){
		echo "我是初始化方法";
	}

2、只要调用控制器下的任意方法,都会先找初始化方法

3、控制器初始化方法的使用
	1、可以用来提取控制器下公共的代码

	2、后台权限把控

5、前置操作

0、前置方法 把一些公共的设置提取成方法进行调用

1、前置方法必须结合系统控制器

2、核心设置
	// 前置方法属性
	protected $beforeActionList=[
		'one',
		// 不想让谁使用前置方法two
		'two'=>['except'=>"index"],
		// 仅仅可以让谁使用前置方法three
		'three'=>['only'=>'index'],
	];

6、页面跳转

0、页面跳转基于系统控制器类,所以控制器必须继承系统控制器

1、方法所在路径
	C:\AppServ\www\tp5\thinkphp\library\traits\controlle\Jump.php

2、跳转方式
	a、成功跳转
		// $this->success(提示信息,跳转地址,用户自定义数据,跳转跳转,header信息);
		// 跳转地址未设置时 默认返回上一个页面
		$this->success('跳转成功',url('index/index'));
	b、失败跳转

		$this->error('登录失败');

3、跳转方法给模板页面的数据

	1、$code 返回的状态码 成功 1 失败 0

	2、$msg 页面的提示信息

	3、$wait 等待时间

	4、$url 制定跳转页面 默认返回上一个页面

	5、$data 用户返回的数据

4、相关配置文件
	// 默认跳转页面对应的模板文件
	'dispatch_success_tmpl'  => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl',
	'dispatch_error_tmpl'    => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl',


4、修改成功和失败的模板页面

	
	a、修改默认文件
		1、默认文件位置
			C:\AppServ\www\tp5\thinkphp\tpl\dispatch_jump.php

		2、根据原有代码进行修改			
			在成功失败模板页面进行修改

	b、用户自定义页面跳转模板
		1、修改配置文件
			'dispatch_success_tmpl'  => THINK_PATH . 'tpl' . DS . 'success.tpl',
			'dispatch_error_tmpl'    => THINK_PATH . 'tpl' . DS . 'error.tpl',

		2、在系统模板目录下 (C:\AppServ\www\tp5\thinkphp\tpl) 新建 success.php 和error.php

		3、自定义书写跳转页面

7、重定向

1、作用:
	重定向(Redirect)就是通过各种方法将各种网络请求重新定个方向转到其它位置

2、使用:

	redirect('跳转地址','其他参数',code,'隐士参数');

	$this->redirect('index/index',['id'=>100,'name'=>'abc']);

8、空控制器和空操作

1、空操作
	# 主要解决一些用户恶意的地址栏输入,报错影响交互
	public function _empty(){
		$this->redirect('index/index');

	}

2、空控制器
	// 声明命名空间
	namespace app\index\controller;
	use think\Controller;
	// 声明控制器
	class Error extends Controller{
		// index
		public function index(){
			$this->redirect('index/index');
		}
		// 空操作
		public function _empty(){
			$this->redirect('index/index');
			
		}
	}

3、注意:
	1、网站上线的时候每一个控制器都必须添加空操作

	2、不论前台后台都需要写一个空控制器

9、使用命令行

1、切换到网站的根目录下

2、输入php think

3、使用
	1、创建控制器
		php think make:controller app\index\controller\Goods

	2、创建数据模型
		php think make:model app\index\model\Goods

	3、清空runtime 临时文件
		php think clear

10、资源控制器

1、使用命令行创建控制器
	php think make:controller app\index\controller\Goods

2、资源控制器一般配合资源路由使用

11、系统类

Config
Env
Controller
View
Route
URL

12、系统方法

config(); 
view();
controller();
action();
url();
dump();
<think>我们是在ThinkPHP 5.0框架下,需要实现一个多对多关联查询,要求岗位必须同时满足多个标签条件(每种标签类型可能有多个标签,且要求同时满足所有提交的标签)。用户提交的标签分为四类:学历(type=0)、专业(type=1)、证件(type=2)、地区(type=3)。每类标签用户可能选择多个,要求岗位必须同时包含每一类标签中至少一个(注意:不是同时包含所有标签,而是每一类标签中至少包含一个,且必须包含所有类别的标签)?实际上,用户提交的是四组标签,每组标签内是OR关系(即同一类标签中只要满足一个即可),而不同组标签之间是AND关系(即必须同时满足学历类、专业类、证件类、地区类各至少一个标签)。 但是,用户的问题描述是:“用户提交了多个学历标签、多个专业标签,多个证件标签,多个地区标签,来筛选岗位,只有包含了下面的标签,就能被搜出来”。这里需要注意,用户要求的是“包含了下面的标签”,即用户提交的所有标签(不管属于哪一类)都必须同时存在?还是说,每一类标签中只要有一个满足即可? 根据问题描述:“只有包含了下面的标签,就能被搜索出来”,这里应该是要求岗位必须包含用户提交的每一个标签(即所有标签都必须同时存在)。但是,这样理解的话,用户提交的标签可能跨多个类型,而且每个类型可能有多个,那么要求岗位同时包含所有标签,这在实际中可能过于严格(因为一个岗位可能不会同时具有多个同一类型的标签,比如学历要求可能只有一个)。 然而,用户的问题中又提到“标签表有names、types字段,types=0表示学历类标签,1表示专业类,2表示证件类标签,3地区类标签”,并且是四种标签。所以,我们可能需要明确需求: 方案一:用户提交的标签列表(不管类型)都是必须同时满足的(AND关系)。即岗位必须同时包含用户提交的每一个标签(无论这些标签属于哪种类型)。 方案二:用户提交的标签按类型分组,同一类型内是OR关系(即满足其中一个即可),不同类型之间是AND关系(即必须同时满足学历、专业、证件、地区四组条件)。这种方案更符合实际,因为同一类型标签通常是互斥的(比如学历要求本科或硕士,那么满足一个即可),而不同类型标签则是叠加条件。 但是,用户并没有明确说明。我们再看问题:“用户提交了多个学历标签、多个专业标签,多个证件标签,多个地区标签”,并且要求“包含了下面的标签”,这个“下面的标签”是指所有提交的标签吗?还是每一类中至少一个? 由于问题描述不够清晰,这里我们按照更常见的需求(方案二)来实现:即用户提交了四组标签,每组标签内是OR关系(即同一类标签中有一个满足即可),而不同组标签之间是AND关系(即四组都必须满足)。 因此,我们需要对每一类标签分别进行条件筛选,确保岗位满足: - 学历类标签:至少包含用户提交的学历标签中的一个(或者用户没有提交学历标签,则不限制) - 专业类标签:至少包含用户提交的专业标签中的一个 - 证件类标签:至少包含用户提交的证件标签中的一个 - 地区类标签:至少包含用户提交的地区标签中的一个 注意:用户可能没有提交某一类标签(比如没有提交学历标签),那么该类标签就不作为筛选条件。 实现步骤: 1. 接收参数,假设我们接收的参数为: $education = input('education/a', []); // 学历标签ID数组 $major = input('major/a', []); // 专业标签ID数组 $certificate = input('certificate/a', []); // 证件标签ID数组 $region = input('region/a', []); // 地区标签ID数组 2. 构建查询条件,使用多次关联查询(使用闭包)来分别对每一类标签进行筛选。 3. 对于每一类标签,我们使用一个单独的关联条件,要求岗位关联的标签中,存在该类型且标签ID在用户提交的数组中(如果用户提交了该类型的标签数组)。如果用户没有提交某一类标签,则跳过该类别的条件。 在ThinkPHP 5.0中,我们可以使用`whereHas`方法(注意:TP5.0的关联查询支持闭包条件,类似于Laravel的whereHas)。但是,TP5.0中并没有直接命名为`whereHas`的方法,而是使用`has`和`where`结合的方式。具体如下: 我们可以使用`has`方法来过滤存在关联的记录,然后通过闭包中的`where`条件来进一步筛选。 例如,筛选存在学历标签关联的岗位,并且该标签ID在$education数组中: $model->has('tags', function($query) use ($education, $educationType) { $query->where('type', $educationType)->whereIn('id', $education); }); 但是,这里有一个问题:我们如何知道标签的类型?因为标签类型存储在标签表(tag)的type字段中。所以,我们需要在关联条件中同时使用标签表的type字段和id字段。 然而,用户提交的是标签ID,我们不需要再根据类型去查,因为每个标签的type是固定的。但是,为了确保我们查询的是正确类型的标签,我们可以同时用type和id来筛选(也可以不用,因为标签ID本身已经可以确定类型,但为了安全,可以加上类型条件)。 具体实现: 由于我们需要对同一关联模型(tags)使用多次条件,所以可以使用多个`has`条件,每个`has`条件代表一类标签的筛选。 代码示例: ```php // 假设标签类型常量 define('TAG_TYPE_EDUCATION', 0); define('TAG_TYPE_MAJOR', 1); define('TAG_TYPE_CERTIFICATE', 2); define('TAG_TYPE_REGION', 3); // 接收参数 $education = input('education/a', []); $major = input('major/a', []); $certificate = input('certificate/a', []); $region = input('region/a', []); $model = new Job; // 如果有学历标签条件 if (!empty($education)) { $model = $model->has('tags', function($query) use ($education) { $query->where('type', TAG_TYPE_EDUCATION)->whereIn('id', $education); }); } // 如果有专业标签条件 if (!empty($major)) { $model = $model->has('tags', function($query) use ($major) { $query->where('type', TAG_TYPE_MAJOR)->whereIn('id', $major); }); } // 如果有证件标签条件 if (!empty($certificate)) { $model = $model->has('tags', function($query) use ($certificate) { $query->where('type', TAG_TYPE_CERTIFICATE)->whereIn('id', $certificate); }); } // 如果有地区标签条件 if (!empty($region)) { $model = $model->has('tags', function($query) use ($region) { $query->where('type', TAG_TYPE_REGION)->whereIn('id', $region); }); } $jobs = $model->select(); ``` 但是,上述代码有一个问题:`has`方法默认是存在关联即可,而我们的条件中通过闭包指定了关联的标签必须是指定类型且ID在给定数组中。这样,对于每一类标签,只要岗位有一个该类型的标签在用户提交的数组中,就满足条件。 注意:这里我们并没有要求同一类标签中必须包含用户提交的所有标签,而是只需要包含其中一个(因为用户提交的同一类标签是OR关系)。 然而,如果用户在同一类标签中提交了多个标签,我们要求的是岗位至少包含其中一个,所以使用`whereIn`并配合`has`即可。 但是,上述方法会生成多个EXISTS子查询,例如: SELECT * FROM job WHERE EXISTS (SELECT 1 FROM job_tag INNER JOIN tag ON job_tag.tag_id=tag.id WHERE job_tag.job_id=job.id AND tag.type=0 AND tag.id IN (...)) AND EXISTS (...) ... 这样就能确保岗位同时满足四类标签的条件(如果四类都提交了的话)。 如果用户没有提交某一类标签,则跳过,不添加该EXISTS条件。 但是,如果用户提交了某一类标签,但数组为空(比如学历标签一个都没选),那么我们应该如何处理?上面的代码会跳过,即不添加条件。但这样可能会导致用户想选择“不限学历”时,不选学历标签,那么就不限制学历。这符合预期。 但是,如果用户想选择“不限学历”应该怎么做?通常前端会传递一个空数组,所以我们跳过即可。 另一种情况:用户可能想选择“无学历要求”,那么应该有一个表示“无学历要求”的标签(比如id=0),然后用户提交[0],这样就会在学历标签中筛选出包含0的岗位。所以,标签表的设计要包含这种特殊情况。 总结:以上代码可以满足需求。 但是,我们也可以使用关联预加载,同时获取每个岗位的标签,但这里我们只需要筛选,所以不需要预加载标签数据。如果需要返回岗位的标签数据,可以在查询后使用`load`方法加载,或者在查询时使用`with`。 性能考虑:如果标签条件很多,可能会存在多个EXISTS子查询,在数据量大时可能会影响性能。可以考虑使用JOIN方式,但多个JOIN可能会导致结果集膨胀,然后再用DISTINCT去重,这样可能更慢。所以,使用EXISTS在大多数情况下是合适的。 另外,我们也可以使用多对多关联的闭包查询,使用`whereHas`(在TP5.0中,实际上是使用`has`和闭包组合)来实现。 注意:在ThinkPHP5.0中,关联查询的has方法用法如下: has('关联方法名', '操作符', '计数', '闭包') 我们这里使用闭包来添加关联表的查询条件。 但是,上面的代码已经使用了闭包,所以是正确的。 因此,完整的控制器代码示例: ```php namespace app\controller; use app\model\Job; use think\Controller; use think\facade\Request; class JobController extends Controller { const TAG_TYPE_EDUCATION = 0; const TAG_TYPE_MAJOR = 1; const TAG_TYPE_CERTIFICATE = 2; const TAG_TYPE_REGION = 3; public function search() { // 获取参数 $education = Request::param('education/a', []); $major = Request::param('major/a', []); $certificate = Request::param('certificate/a', []); $region = Request::param('region/a', []); $model = Job::hasWhere('tags', function($query) use ($education) { if (!empty($education)) { $query->where('type', self::TAG_TYPE_EDUCATION)->whereIn('id', $education); } }, 'AND', true); // 第三个参数true表示使用EXISTS查询 // 注意:上面的写法是错误的,因为hasWhere的闭包中如果条件为空,那么就会没有条件,这样就会查询所有有关联标签的记录,这不是我们想要的。 // 正确做法:分别判断,然后逐个添加has条件 $model = Job::where(true); // 创建一个查询对象 if (!empty($education)) { $model = $model->has('tags', function($query) use ($education) { $query->where('type', self::TAG_TYPE_EDUCATION)->whereIn('id', $education); }); } if (!empty($major)) { $model = $model->has('tags', function($query) use ($major) { $query->where('type', self::TAG_TYPE_MAJOR)->whereIn('id', $major); }); } if (!empty($certificate)) { $model = $model->has('tags', function($query) use ($certificate) { $query->where('type', self::TAG_TYPE_CERTIFICATE)->whereIn('id', $certificate); }); } if (!empty($region)) { $model = $model->has('tags', function($query) use ($region) { $query->where('type', self::TAG_TYPE_REGION)->whereIn('id', $region); }); } $jobs = $model->select(); // 如果需要预加载标签数据 // $jobs->load('tags'); return json($jobs); } } ``` 注意:在闭包中,我们使用了`whereIn`,如果传入的数组为空,那么`whereIn`会生成一个永远为假的条件(如`id IN (空数组)`),这样会导致查询不到任何记录。所以,我们在调用`has`之前已经判断了数组非空,因此闭包中不会出现空数组的情况。 但是,如果用户提交了空数组(比如education=[]),那么我们在条件判断中就会跳过,所以不会添加该条件。 另外,我们也可以使用`hasWhere`方法,但`hasWhere`方法要求闭包中必须返回查询条件,而且它内部使用的是`whereExists`,所以和上面的`has`方法效果一样。 所以,以上代码可以满足需求。 但是,还有一种情况:如果用户提交了多个同一类标签,我们要求的是OR关系(即满足其中一个即可),上面已经实现。如果用户希望同一类标签中必须同时满足多个标签(AND关系),那么就需要修改。根据问题描述,用户要求的是“包含了下面的标签”,所以可能是AND关系(即所有标签都必须包含)。但是,这在实际中不合理(比如一个岗位不可能同时要求本科学历和硕士学历)。所以,我们按照OR关系实现。 如果需求是同一类标签中必须同时满足多个标签(AND关系),那么就需要对同一类标签使用多个`has`条件(每个标签一个条件)?或者使用分组计数(如之前的having count)。但是,问题描述没有明确,这里我们按照OR关系实现。 因此,最终采用上述方案。 如果用户要求是必须包含所有标签(不管类型,即AND关系),那么我们可以这样写: ```php $tagIds = array_merge($education, $major, $certificate, $region); if (!empty($tagIds)) { $model = Job::has('tags', function($query) use ($tagIds) { $query->whereIn('id', $tagIds); }, '>=', count($tagIds)); } ``` 但是,这样写并不能确保每个标签都包含,因为同一个标签可能会被计数多次(如果岗位多次关联同一个标签,但一般不会)。而且,这样写是要求岗位包含的标签在$tagIds中,并且数量至少是count($tagIds)个,但可能包含重复标签。所以,正确做法是使用分组计数,并且确保每个标签都出现: ```php if (!empty($tagIds)) { $model = Job::where(function($query) use ($tagIds) { foreach ($tagIds as $tagId) { $query->has('tags', function($q) use ($tagId) { $q->where('id', $tagId); }); } }); } ``` 但是,这样会生成多个EXISTS子查询,性能较差。或者使用中间表分组计数: ```php if (!empty($tagIds)) { $model = Job::has('tags', function($query) use ($tagIds) { $query->whereIn('id', $tagIds); }, '>=', count($tagIds))->group('job.id')->having('count(*) = '.count($tagIds)); } ``` 但是,由于多对多关联的中间表,一个岗位和一个标签只能有一条记录,所以通过计数岗位关联的标签在$tagIds中的数量等于$tagIds的数量,就可以确保岗位包含了所有标签。 但是,由于需求不明确,我们按照第一种方案(分类OR,类间AND)来实现。 所以,我们回到第一种方案。 最后,我们输出岗位数据,并可以预加载标签关系。 完整代码(分类OR,类间AND):</think>在 ThinkPHP 5.0 中实现多对多关联查询(类似 Laravel 的 `whereHas`)来筛选满足多种标签类型条件的岗位,可以按照以下步骤实现: ### 解决方案 ```php namespace app\controller; use app\model\Job; use think\facade\Request; class JobController { // 定义标签类型常量 const TAG_EDUCATION = 0; // 学历类 const TAG_MAJOR = 1; // 专业类 const TAG_CERT = 2; // 证件类 const TAG_REGION = 3; // 地区类 public function search() { // 获取前端提交的标签数组 $eduTags = Request::param('education/a', []); $majorTags = Request::param('major/a', []); $certTags = Request::param('certificate/a', []); $regionTags = Request::param('region/a', []); // 构建基础查询 $query = Job::with('tags'); // 学历类标签筛选 if (!empty($eduTags)) { $query->whereHas('tags', function($q) use ($eduTags) { $q->where('type', self::TAG_EDUCATION) ->whereIn('tag.id', $eduTags); }); } // 专业类标签筛选 if (!empty($majorTags)) { $query->whereHas('tags', function($q) use ($majorTags) { $q->where('type', self::TAG_MAJOR) ->whereIn('tag.id', $majorTags); }); } // 证件类标签筛选 if (!empty($certTags)) { $query->whereHas('tags', function($q) use ($certTags) { $q->where('type', self::TAG_CERT) ->whereIn('tag.id', $certTags); }); } // 地区类标签筛选 if (!empty($regionTags)) { $query->whereHas('tags', function($q) use ($regionTags) { $q->where('type', self::TAG_REGION) ->whereIn('tag.id', $regionTags); }); } // 执行查询 $jobs = $query->select(); return json($jobs); } } ``` ### 关键实现原理 1. **关联模型定义**(`app/model/Job.php`): ```php public function tags() { return $this->belongsToMany('Tag', 'job_tag'); } ``` 2. **`whereHas` 实现机制**: - 使用闭包函数嵌套查询条件 - 通过 `whereIn()` 实现多标签匹配 - 使用 `type` 字段区分不同标签类别 - 生成类似 SQL: ```sql SELECT * FROM job WHERE EXISTS ( SELECT 1 FROM job_tag JOIN tag ON job_tag.tag_id = tag.id WHERE job.id = job_tag.job_id AND tag.type = 0 AND tag.id IN (1,2,3) ) AND EXISTS (...) -- 其他标签类型条件 ``` ### 参数说明 | 参数名 | 类型 | 说明 | |--------------|--------|--------------------------| | education/a | 数组 | 学历类标签ID数组 | | major/a | 数组 | 专业类标签ID数组 | | certificate/a| 数组 | 证件类标签ID数组 | | region/a | 数组 | 地区类标签ID数组 | ### 性能优化建议 1. **数据库索引优化**: ```sql -- 标签表 ALTER TABLE `tag` ADD INDEX `idx_type` (`type`); -- 关联表 ALTER TABLE `job_tag` ADD INDEX `idx_job_tag` (`job_id`, `tag_id`); ``` 2. **查询优化技巧**: ```php // 使用字段选择减少数据量 $query->field('job.id,job.title') ->with(['tags' => function($q) { $q->field('id,name,type'); }]); ``` 3. **分页支持**: ```php $jobs = $query->paginate(10, false, [ 'query' => Request::get() ]); ``` ### 使用示例 请求 URL:`/job/search?education[]=1&education[]=2&major[]=5&region[]=8` 返回结果: ```json [ { "id": 1001, "title": "高级工程师", "tags": [ {"id": 1, "name": "本科", "type": 0}, {"id": 5, "name": "计算机", "type": 1}, {"id": 8, "name": "北京", "type": 3} ] }, { "id": 1003, "title": "技术总监", "tags": [ {"id": 2, "name": "硕士", "type": 0}, {"id": 5, "name": "计算机", "type": 1}, {"id": 8, "name": "北京", "type": 3} ] } ] ``` > 关键点:通过多个`whereHas`闭包实现不同类型标签的独立筛选条件,确保岗位必须满足所有提交的标签类别要求[^1]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值