• 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

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

  • 相关阅读:
    基础薄弱的反思
    最短路SPFA
    乌龟棋
    石子归并
    Linux学习2
    java 基础 数组
    java 基础 异常
    java 基础 接口
    java 基础 instance of
    solidity“abi.encode/abi.encodePacked”使用golang编码
  • 原文地址:https://www.cnblogs.com/xcymn/p/15712386.html
Copyright © 2020-2023  润新知