1.控制器类Controller介绍
tp5中的控制器当然是在系统类库中 , 在thinkphp在的library下的think目录下
<?php
namespace think; // 所有系统类库的命名空间都是think
\think\Loader::import('controller/Jump', TRAIT_PATH, EXT); // 使用Loader下的import方法 , 兼用php5.4
use think\exception\ValidateException; // 加载错误异常类的命名空间
class Controller // 类的声明
{
use \traits\controller\Jump; // 引入指定类库 , 加载traits类库集到当前类中
/**
* @var \think\View 视图类实例
*/
protected $view; // 可以直接使用这个属性引用视图类的方法,加载模板赋值等
/**
* @var \think\Request Request实例
*/
protected $request; // 可以直接使用这个属性获取请求对象的所有信息, 比如路由变量,参数信息
// 验证失败是否抛出异常
protected $failException = false;
// 是否批量验证
protected $batchValidate = false;
/**
* 前置操作方法列表
* @var array $beforeActionList
* @access protected
*/
protected $beforeActionList = []; // 数组 , 前置方法就是调用某个方法前先调用这个方法
// 这些属性都是私有的属性 , 只能在类中使用或者类的子类中使用 , 在外面无法调用
/**
* 架构函数
* @param Request $request Request对象
* @access public
*/
public function __construct(Request $request = null) // 构造方法
// 主要做了四件事 , 创建了视图对象view , 请求对象 request 控制器初始化 前置操作方法
{
if (is_null($request)) {
$request = Request::instance();
}
$this->view = View::instance(Config::get('template'), Config::get('view_replace_str'));
$this->request = $request;
// 控制器初始化
$this->_initialize();
// 前置操作方法
if ($this->beforeActionList) {
foreach ($this->beforeActionList as $method => $options) {
is_numeric($method) ?
$this->beforeAction($options) :
$this->beforeAction($method, $options);
}
}
// 初始化
protected function _initialize()
{
}
/**
* 前置操作
* @access protected
* @param string $method 前置操作方法名
* @param array $options 调用参数 ['only'=>[...]] 或者['except'=>[...]]
*/
protected function beforeAction($method, $options = [])
{
if (isset($options['only'])) {
if (is_string($options['only'])) {
$options['only'] = explode(',', $options['only']);
}
if (!in_array($this->request->action(), $options['only'])) {
return;
}
} elseif (isset($options['except'])) {
if (is_string($options['except'])) {
$options['except'] = explode(',', $options['except']);
}
if (in_array($this->request->action(), $options['except'])) {
return;
}
}
call_user_func([$this, $method]);
}
/**
* 加载模板输出
* @access protected
* @param string $template 模板文件名
* @param array $vars 模板输出变量
* @param array $replace 模板替换
* @param array $config 模板参数
* @return mixed
*/
protected function fetch($template = '', $vars = [], $replace = [], $config = []) // 最用的方法,加载模板文件
{
return $this->view->fetch($template, $vars, $replace, $config);
}
/**
* 渲染内容输出 , 直接渲染输出 , 不用加载模板文件 , 一些简单的操作比fetch好用, 他们都是调用view下的方法
* @access protected
* @param string $content 模板内容
* @param array $vars 模板输出变量
* @param array $replace 替换内容
* @param array $config 模板参数
* @return mixed
*/
protected function display($content = '', $vars = [], $replace = [], $config = [])
{
return $this->view->display($content, $vars, $replace, $config);
}
/**
* 模板变量赋值
* @access protected
* @param mixed $name 要显示的模板变量
* @param mixed $value 变量的值
* @return void
*/
protected function assign($name, $value = '') // 给模板变量赋值的
{
$this->view->assign($name, $value);
}
/**
* 初始化模板引擎
* @access protected
* @param array|string $engine 引擎参数
* @return void
*/
protected function engine($engine) // 初始化模板引擎的
{
$this->view->engine($engine);
}
/**
* 设置验证失败后是否抛出异常
* @access protected
* @param bool $fail 是否抛出异常
* @return $this
*/
protected function validateFailException($fail = true) // 验证失败抛出异常的方法
{
$this->failException = $fail;
return $this;
}
/**
* 验证数据
* @access protected
* @param array $data 数据
* @param string|array $validate 验证器名或者验证规则数组
* @param array $message 提示信息
* @param bool $batch 是否批量验证
* @param mixed $callback 回调方法(闭包)
* @return array|string|true
* @throws ValidateException
*/
protected function validate($data, $validate, $message = [], $batch = false, $callback = null)
// 批量验证的
{
if (is_array($validate)) {
$v = Loader::validate();
$v->rule($validate);
} else {
if (strpos($validate, '.')) {
// 支持场景
list($validate, $scene) = explode('.', $validate);
}
$v = Loader::validate($validate);
if (!empty($scene)) {
$v->scene($scene);
}
}
// 是否批量验证
if ($batch || $this->batchValidate) {
$v->batch(true);
}
if (is_array($message)) {
$v->message($message);
}
if ($callback && is_callable($callback)) {
call_user_func_array($callback, [$v, &$data]);
}
if (!$v->check($data)) {
if ($this->failException) {
throw new ValidateException($v->getError());
} else {
return $v->getError();
}
} else {
return true;
}
}
}
1.1五个属性
1、视图类实例对象: protected $view;
2、请求类实例对象: protected $request;
3、验证失败是否抛出异常: protected $failException=false
4、是否批量验证: protected $batchValidate= false;
5、前置方法列表: protected $beforeActionlist=[ ];
1.2八个方法
1、构造方法: public function __construct(Request $request = null)
2、初始化方法: protected function _initialize() 默认为空,在子类中重写这个方法
3、加载模板; protected function fetch($template = '', $vars = [], $replace = [], $config = [])
4、渲染内容: protected function display($content = '', $vars = [], $replace = [], $config = [])
5、模板变量赋值: protected function assign($name, $value = '')
6、初始化模板引擎:protected function engine($engine)
7、验证失败抛出异常: protected function validateFailException($fail = true)
8、验证数据: protected function validate($data, $validate, $message = [], $batch = false, $callback = null)
任何学习方法,都替代不了阅读源代码。阅读源代码,不仅可以了解作者的编程思路,还可以学到很多编程技巧,是一种非常有效的学习方式
2.控制器初体验
2.1控制器
什么是控制器,什么是可访问控制器
控制器就是mvc中的c , controller中的首字母 , 用于读取视图view , 完成用户输入 , 处理数据model
可访问控制器就是可以通过URL访问到的控制器 , 也就是我们常说的控制器
在惯例配置文件中有关于控制器层的设置
// 默认的访问控制器层
'url_controller_layer' => 'controller',
// 这个指的就是application目录下的index目录下的controller文件夹 , 如果在配置文件中把它修改了 , 所有以前用controller的地方都要修改为与之对应的名称
2.2操作
什么是操作 , 什么是可访问操作
操作就是定义在控制器中的类方法
可访问操作就是类中被public关键字限制的方法 , protected和private都是不可访问方法
2.3命名空间
什么是命名空间?它与控制器或者方法后缀的关系
命名空间用来划分同名称类在不同的路径下
namespace app\index\controller
// 命名空间=根空间+子空间(可选)+类名 app是内置的根命名空间
在tp3.2.3中控制器后面是有后缀的 , 在tp5中关闭的 , 在惯例配置文件中可以设置 , 操作方法也可以添加后缀, 在tp5默认是空的, 在配置文件中 action_suffix => 'Action',
class IndexController
public function listAction(){} // 但是在路由中访问该方法不需要在操作后面加Action , 只需要list
2.4命名规范
控制器类的命名规范采用的是驼峰法 , 首字母大写 , 如果是两个单词 , 第二个单词也要大写 , 然后访问的时候是下划线拼接 , 默认会把下划线转换 , 在配置文件中
在tp5中所有大小写字母都会转换成小写
示例:
<?phpnamespace app\index\controller;class UserLogin{ public function index() { return 'UserLogin::index'; }}
3.控制器进阶
3.1自定义访问控制器层
默认是controller 在 think下的惯例配置文件 , 这样更加灵活
// 默认的访问控制器层 'url_controller_layer' => 'controller',// 如果修改了controller , 那么就需要在每个模块下也要修改 , 默认的控制器层是controller
示例 : 在自定义配置文件中修改为api
'url_controller_layer' => 'api'
3.2多级控制器
多级控制器 指的就是controller目录的子目录 , 当一个控制器层下有多个类的时候 , 可以通过多级控制器进行分类管理
示例:
3.3空操作和空控制器
空操作用来提示用户 , 当用户访问的操作不存在会触发空操作
示例 :
public function _empty($method) { return '你访问对的'.$method.'方法不存在'; }
空控制器和空方法类似 , 也是当用户访问的控制器不存在的时候 , 触发提示用户的 , 在惯例配置文件中设置的有空的控制器名称 , 当然也是可以自定义的
'empty-controller' => 'Error' ,
然后在controller下新建一个Error类
示例
<?phpnamespace app\index\controller;class Error{ public function test() { return '当前访问的控制器类不存在'; } public function _empty($method) { return '不存在404'; }}
一般情况下空操作都是和空控制器配合使用
3.4trait
trait类库 , 怎么使用? 直接在控制器类中引用
3.5单一模块
就是不自定义模块 , 可以直接删除index目录 , 然后在application下新建一个controller目录 , 然后在该目录下直接新建控制器类 , 注意类命名空间不需要模块了 , 需要在配置文件中关闭多级模块
// 是否支持多模块 'app_multi_module' => false, // 默认是true开启的
单模块文件
<?phpnamespace app\controller;class Demo{ public function index() { return '单模块下的index方法'; }}
本课是站在使用者角度来思考问题,控制器的分级管理使项囯逻辑更加清晰,空操作与空操作器使控制器具备了容错机制,ta的引入,使控制器可从多tra类中继承方法集,横向扩展了控制器的功能。
4.公共操作与公共控制器
在tp5中 , 控制器类不需要继承任何类就可以工作了 ,那为什么还要有controller类呢?
4.1公共操作
公共操作是就是一个控制器中某个操作的返回值, 会影响到所有的操作, 或者他创建的数据可以被所有操作共享 , 他就是构造方法
示例
protected $lesson; public function __construct(Request $request = null,$lesson='php5') { parent::__construct($request); $this->lesson = $lesson; } public function test1(){ return $this->lesson; // return (new self(null,'phpnb')) ->lesson; } public function test2(){ return $this->lesson; }
可以将公共操作写在controller类中的初始化方法中 , 然后控制器类继承这个controller类 , 初始化方法会在构造方法里面调用
use think\Controller;class Index extends Controller protected $lesson; public function _initialize($lesson='tp5 nb') { parent::_initialize(); // TODO: Change the autogenerated stub $this->lesson = $lesson; } public function test1(){ return (new self(null,'phpnb')) ->lesson; } public function test2(){ $this->_initialize('alex nb'); return $this->lesson; }
4.2公共控制器类
公共操作如果比较多 , 可以封装到一个类中 , 这个类就是公共控制器类 , 然后控制器类继承这个类 , 这个类是在基类和控制器中间的一层类 , 这个类还要继承Controller类
在controller目录下新建一个Base类
<?phpnamespace app\index\controller;class Base extends \think\Controller{ protected $siteName = 'php中文网'; public function test() { return '欢迎来到'.$this->siteName.'学习'; }}
<?phpnamespace app\index\controller;class Index extends \app\index\controller\Base{ public function ceshi() { return $this->test(); }}
控制器中的公共操作,解决了类中数据共享以及属性初始化的问题。公共控制器,从控制器层面上,解决了控制器类之间的属性和方法的共享问题。这是二层面上的抽象,也是非常实用的技术 , 感觉就是把相同的属性或者说都要使用的属性封装到一个类中 , 这不就是面向对象的三大特性之一
5.前置操作
什么是前置操作 ?
顾名思义就是某个操作之前必须先执行他才可以 , 有点django中的中间件的味道 , 类中的构造方法也是 , 是其他操作执行前必须先执行他
<?phpnamespace app\index\controller;class Index extends \think\Controller{ protected $beforeActionList =[ 'before1'=>'' // 为空,表示before1是当前类中的全部操作的前置操作 ]; // 前置方法列表 protected function before1() { $this->siteName = 'php中文网'; } protected $siteName; // 自定义属性 public function demo1() { return $this->siteName; } public function demo2() { return $this->siteName; } public function demo3() { return $this->siteName; }}
访问demo1 , 2 , 3 都会先执行前置方法列表中的before1方法
只给demo2设置一个前置方法 , 然后设置一个除了demo2和demo1都生效
<?phpnamespace app\index\controller;class Index extends \think\Controller{ protected $beforeActionList =[ 'before1'=>'', // 为空,表示before1是当前类中的全部操作的前置操作 'before2'=>['only'=>'demo2'], // before2只对demo2有操作 'before2'=>['except'=>'demo2','demo1'] // before2仅对除了demo2和demo1之外有操作 ]; // 前置方法列表 protected function before1() { $this->siteName = 'php中文网'; } protected function before2() { $this->siteName = 'thinkphp5 nb'; } protected function before3() { $this->siteName = 'alex nb'; } protected $siteName; // 自定义属性 public function demo1() { return $this->siteName; } public function demo2() { return $this->siteName; }}
在之前的版本中,除了前置操作,还有后置操作,不过在ThinkPHP5中,已经取消了形同鸡肋的后置操作。关于
前置操作,就把想象成全部或部分方法的构造函数好了。其根本目的,还是为了项目规范,易维护,好扩展。
6.页面跳转和重定向
调用方法:$this->success(提示,地址)和$this->error(提示,地址)页面跳转的地址可以是: 1.当前控制器 2.跨控制器 3.跨模块 4.外部地址想要页面跳转 , 控制器类就要继承controller基类, 但是并没有success方法和error方法 , 这两个是通过traits类库引用的 , 同时重定向redirect方法也在这个里面
示例 : 在同一个控制器中
<?phpnamespace app\index\controller;class Index extends \think\Controller{ public function demo1($name=null) { if($name=='thinkphp'){ $this->success('验证成功,正在跳转','ok'); // 参数: 提示信息 , 跳转的url }else{ $this->error('验证失败,正在跳转','login'); } } public function ok() { return '欢迎来到后台页面'; } public function login() { return '请登录'; }}
示例 : 跨控制器调用
<?phpnamespace app\index\controller;class Index extends \think\Controller{ public function demo1($name=null) { if($name=='thinkphp'){ $this->success('验证成功,正在跳转','login/ok'); }else{ $this->error('验证失败,正在跳转','login/login'); } }}
<?phpnamespace app\index\controller;class Login extends \think\Controller{ public function ok() { return '欢迎来到后台页面'; } public function login() { return '请登录'; }}
示例 : 跨模块调用
<?phpnamespace app\edu\controller;class Login extends \think\Controller{ public function ok() { return '欢迎来到后台页面'; } public function login() { return '请登录'; }}
<?phpnamespace app\index\controller;class Index extends \think\Controller{ public function demo1($name=null) { if($name=='thinkphp'){ $this->success('验证成功,正在跳转','edu/login/ok'); }else{ $this->error('验证失败,正在跳转','edu/login/login'); } }}
示例 : 调转到外部地址
<?phpnamespace app\index\controller;class Index extends \think\Controller{ public function demo1($name=null) { if($name=='thinkphp'){ $this->success('验证成功,正在跳转','http://www.baidu.com'); // $this->success('验证成功,正在跳转',\think\Url::build('edu/login/ok')); }else{ $this->error('验证失败,正在跳转','edu/login/login'); } }}
重定向
调用方法:$this->redirect((路由地址,变量列表,后缀,域名开关)1.站内地址2.外部地址
示例 : 重定向站内地址
<?phpnamespace app\index\controller;class Index extends \think\Controller{ public function demo1($name=null) { if($name=='tom'){ // $this->success('验证成功,正在跳转','edu/login/ok');// $this->redirect('http://www.jd.com'); $this->redirect('ok',['siteName'=>'thinkphp5']); }else{ $this->error('验证失败,正在跳转','edu/login/login'); } } public function ok($siteName){ return '欢迎来到'.$siteName.'学习'; }}
示例 : 重定向到指定地址
<?phpnamespace app\index\controller;class Index extends \think\Controller{ public function demo1($name=null) { if($name=='thinkphp'){ $this->redirect('http://www.jd.com',302); // 302是临时重定向 , 301永久重定向 }else{ $this->error('验证失败,正在跳转','edu/login/login'); } }}
页面跳转和重定向非常简单和实用
7.请求对象和参数绑定
7.1请求变量和请求对象
请求变量和请求对象的关系 , 请求对象可以处理请求变量 , 但是不局限于请求变量 , 只要是请求相关的 , 请求对象都可以处理
<?phpnamespace app\index\controller;class Index extends \think\Controller{ public function index() { return '欢迎来到php中文网学习'; } public function hello($name,$lesson) { return '这里会教会你'.$name.'的'.$lesson; }}
请求对象 : 需要先实例化请求类获取请求对象
<?phpnamespace app\index\controller;use think\Request;class Index extends \think\Controller{ public function index() { return '欢迎来到php中文网学习'; } public function hello($name,$lesson,$age=1) { $request = \think\Request::instance(); // 获取一个请求对象 dump($request->get()); // 获取所有get请求参数// dump($request->post()); // 获取所有post请求参数// dump($request->param()); // 获取所有请求参数// dump($request->has('name')); // 查看请求中是否有name变量//// return '这里会教会你'.$name.'的'.$lesson; }}
7.2请求信息
1.url相关2.mvc访问相关3.pathinfo信息4.请求变量的类型相关5.路由相关
请求对象(request)的方法
domain() // 获取域名url() // 获取路由,默认不带域名 , url(true)带有域名path() // 获取urlpathinfo() // 获取urlext() // 获取后缀module() // 获取模块controller() // 获取控制器action() // 获取操作ip() // 获取iponly('name') // 只获取name变量except('id') // 除了id变量 , 其他都获取
按照参数绑定 , 按照顺序绑定
// URL参数方式 0 按名称成对解析 1 按顺序解析 'url_param_type' => 1,
<?phpnamespace app\index\controller;use think\Request;class Index extends \think\Controller{ public function index() { return '欢迎来到php中文网学习'; } public function hello($name,$lesson,$age=1) { $request = Request::instance(); // 获取一个请求对象 dump($request->param()); }}
用户对所有网络资源的访问,都要通过请求对象,以∪RL为载体进行访问。用户需求的个性化是通过请求变量来体现,下节课,介绍请求对象的属性与方法注入,来扩展请求对象的功能,满足用户更多需求
8.请求对象的属性和方法注入
1、属性注入: \think\Request::instance()->属性2、方法注入: \think\Request::hook(方法,对应函数) hook钩子函数 , 我的理解就是装饰器
作用给当前请求对象绑定属性和方法,请求对象在应用的整个生命周期内都有效的,不仅可以被当前所有控制器方法
所共享,还可以跨控制器和模块进行调用。
要注入到请求对象的属性和方法必须写在common.php中
属性注入示例:
<?php// 应用公共文件use think\Request;// 获取请求对象$request = Request::instance();// 属性注入$request->siteName = 'php中文网';// 方法注入function getSiteName(Request $request) // 第一个参数必须是Request类型的变量{ return '站点名称: '.$request->siteName;}// 注册请求对象的方法,也叫钩子Request::hook('getSiteName','getSiteName');
<?phpnamespace app\index\controller;use think\Request;class Index extends \think\Controller{ public function index() { return '欢迎来到php中文网学习'; } public function demo1() { return $this->request->siteName; } public function demo2() { return $this->request->getSiteName(); }}
给请求对象注入的自定义属性与方法,与原请求对象中的属性与方法是同级的,所以在整个应用的生命周期内都是有效的。
请求对象的属性与方法注入,不仅可以扩展请求对象的功能,实现在整个应用周期的信息共享,而且它还为自定义请求的行为制定了标准。
9.对象变量的依赖注入
1. 依赖注入: 改变了使用对象前, 必须先创建对象的传统方式, 而是从外部注入所依赖的对象2. ThinkPHP5依赖注入主要是指把对象注入到可访问控制器3. 注入方式: 控制器的构造方法和操作方法4. 实现方式: 对参数进行对象类型约束则会自动触发依赖注入,自动实例化该对象;
<?phpnamespace app\index\controller;class Index{ public function index() { return '欢迎来到php中文网学习'; } public function demo1($lesson) { return '课程: '.$lesson; } public function demo2() { return '课程: '.$lesson; }}
上面的代码 , 默认demo2是会出错的 , 因为请求对象中并没有共享$lesson变量 , 如果要获得各个方法的变量共享
就需要使用请求对象 , 继承基类controller , 直接实例化请求对象
继承基类
use think\Controller;class Index extends Controller{ public function index() { return '欢迎来到php中文网学习'; } public function demo1($lesson) { return '课程: '.$lesson; } public function demo2() { return '课程: '.$this->request->param('lesson'); }}
实例化请求对象
<?phpnamespace app\index\controller;use think\Controller;class Index extends Controller{ public function index() { return '欢迎来到php中文网学习'; } public function demo1() { return '课程: '.\think\Request::instance()->param('lesson'); } public function demo2() { return '课程: '.$this->request->param('lesson'); }}
依赖注入方式实现 , 就是把request对象当成方法的参数传进去 , 那么哪个方法呢?为了让所有操作都能共享 , 就传递到构造方法中
<?phpnamespace app\index\controller;use think\Request;class Index{ protected $request; public function __construct(Request $request) { $this->request = Request::instance(); } public function index() { return '欢迎来到php中文网学习'; } public function demo1() { return '课程: '.$this->request->param('lesson'); } public function demo2() { return '课程: '.$this->request->param('lesson'); }}
访问控制器的依赖注入,极大了方便了在操作中使用请求对象,它不需要依赖于控制器基类,也不需要导入请求类命名空间,是一种高效的获取请求对象的方式,请同学们一定要重视它