7.thinkphp框架控制器

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'

image-20211108181402610

3.2多级控制器

多级控制器 指的就是controller目录的子目录 , 当一个控制器层下有多个类的时候 , 可以通过多级控制器进行分类管理

示例:

image-20211108184055586

image-20211108181901378

3.3空操作和空控制器

空操作用来提示用户 , 当用户访问的操作不存在会触发空操作

示例 :

 public function _empty($method)    {        return '你访问对的'.$method.'方法不存在';    }

image-20211108184358804

空控制器和空方法类似 , 也是当用户访问的控制器不存在的时候 , 触发提示用户的 , 在惯例配置文件中设置的有空的控制器名称 , 当然也是可以自定义的

'empty-controller' => 'Error' , 

然后在controller下新建一个Error类

示例

<?phpnamespace app\index\controller;class Error{    public function test()    {        return '当前访问的控制器类不存在';    }    public function _empty($method)    {        return '不存在404';    }}

一般情况下空操作都是和空控制器配合使用

3.4trait

trait类库 , 怎么使用? 直接在控制器类中引用

image-20211108191617100

3.5单一模块

就是不自定义模块 , 可以直接删除index目录 , 然后在application下新建一个controller目录 , 然后在该目录下直接新建控制器类 , 注意类命名空间不需要模块了 , 需要在配置文件中关闭多级模块

// 是否支持多模块    'app_multi_module'       => false,   // 默认是true开启的

单模块文件

<?phpnamespace app\controller;class Demo{    public function index()    {        return '单模块下的index方法';    }}

image-20211109092135616

本课是站在使用者角度来思考问题,控制器的分级管理使项囯逻辑更加清晰,空操作与空操作器使控制器具备了容错机制,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;    }

image-20211109093121316

可以将公共操作写在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;    }

image-20211109094249096

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

image-20211109095326169

控制器中的公共操作,解决了类中数据共享以及属性初始化的问题。公共控制器,从控制器层面上,解决了控制器类之间的属性和方法的共享问题。这是二层面上的抽象,也是非常实用的技术 , 感觉就是把相同的属性或者说都要使用的属性封装到一个类中 , 这不就是面向对象的三大特性之一

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方法

image-20211109101023513

只给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 '请登录';    }}

image-20211109104124955

示例 : 跨控制器调用

<?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 '请登录';    }}

image-20211109104516225

示例 : 跨模块调用

<?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');        }    }}

image-20211109104939713

示例 : 调转到外部地址

<?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;    }}

image-20211109111655339

请求对象 : 需要先实例化请求类获取请求对象

<?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());    }}

image-20211109114543327

用户对所有网络资源的访问,都要通过请求对象,以∪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. 实现方式: 对参数进行对象类型约束则会自动触发依赖注入,自动实例化该对象;

image-20211109135402880

<?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');    }}

image-20211109140007220

实例化请求对象

<?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');    }}

image-20211109140227721

依赖注入方式实现 , 就是把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');    }}

image-20211109140843996

访问控制器的依赖注入,极大了方便了在操作中使用请求对象,它不需要依赖于控制器基类,也不需要导入请求类命名空间,是一种高效的获取请求对象的方式,请同学们一定要重视它

posted @ 2021-12-20 18:40  Mn猿  阅读(64)  评论(0编辑  收藏  举报