复习tp3.23(七)(Action参数绑定,空操作和空控制器,初始化、前置和后置操作,页面跳转和重定向,页面请求和AJAX,伪静态,操作绑定到类,多层控制器,自动验证,自动完成)

ThinkPHP3.2.3Action参数绑定

我们陆续为大家介绍下控制器的一些高级特性用法,让你更深入了解ThinkPHP控制器的独特功能。

Action参数绑定

在之前的内容中,涉及的控制器操作方法都是没有任何参数的,其实ThinkPHP可以支持操作方法的参数绑定功能。
Action参数绑定是通过直接绑定URL地址中的变量作为操作方法的参数,可以简化方法的定义甚至路由的解析,其原理是把URL中的参数(不包括模块、控制器和操作名)和控制器的操作方法中的参数(按变量名或者变量顺序)进行绑定。

按变量名绑定

默认的参数绑定方式是按照变量名进行绑定,例如,我们给Blog控制器定义了两个操作方法read和archive方法,由于read操作需要指定一个id参数,archive方法需要指定年份(year)和月份(month)两个参数,那么我们可以如下定义:

<?php
namespace Home\Controller;
use Think\Controller;
class BlogController extends Controller{
    public function read($id){
        echo 'id='.$id;
    }
 
    public function archive($year='2013',$month='01'){
        echo 'year='.$year.'&month='.$month;
    }
}

注意这里的操作方法并没有具体的业务逻辑,只是简单的示范。
URL的访问地址分别是:

http://serverName/index.php/Home/Blog/read/id/5
http://serverName/index.php/Home/Blog/archive/year/2013/month/11

两个URL地址中的id参数和year和month参数会自动和read操作方法以及archive操作方法的同名参数绑定。
变量名绑定不一定由访问URL决定,路由地址也能起到相同的作用
输出的结果依次是:

id=5
year=2013&month=11

**按照变量名进行参数绑定的参数必须和URL中传入的变量名称一致,但是参数顺序不需要一致。**也就是说

http://serverName/index.php/Home/Blog/archive/month/11/year/2013

和上面的访问结果是一致的,URL中的参数顺序和操作方法中的参数顺序都可以随意调整,关键是确保参数名称一致即可。
如果使用下面的URL地址进行访问,参数绑定仍然有效:

http://serverName/index.php?s=/Home/Blog/read/id/5
http://serverName/index.php?s=/Home/Blog/archive/year/2013/month/11
http://serverName/index.php?c=Blog&a=read&id=5
http://serverName/index.php?c=Blog&a=archive&year=2013&month=11

如果用户访问的URL地址是(至于为什么会这么访问暂且不提):

http://serverName/index.php/Home/Blog/read/

那么会抛出下面的异常提示: 参数错误:id
报错的原因很简单,因为在执行read操作方法的时候,id参数是必须传入参数的,但是方法无法从URL地址中获取正确的id参数信息。由于我们不能相信用户的任何输入,因此建议你给read方法的id参数添加默认值,例如:

public function read(KaTeX parse error: Expected '}', got 'EOF' at end of input: … echo 'id='.id;
}

这样,当我们访问 http://serverName/index.php/Home/Blog/read/ 的时候 就会输出

id=0

当我们访问 http://serverName/index.php/Home/Blog/archive/ 的时候,输出:

year=2013&month=01

始终给操作方法的参数定义默认值是一个避免报错的好办法

按变量顺序绑定

第二种方式是按照变量的顺序绑定,这种情况下URL地址中的参数顺序非常重要,不能随意调整。要按照变量顺序进行绑定,必须先设置URL_PARAMS_BIND_TYPE为1:
‘URL_PARAMS_BIND_TYPE’ => 1 ,// 设置参数绑定按照变量顺序绑定
操作方法的定义不需要改变,URL的访问地址分别改成:

http://serverName/index.php/Home/Blog/read/5
http://serverName/index.php/Home/Blog/archive/2013/11

复制
输出的结果依次是:

id=5
year=2013&month=11

这个时候如果改成

http://serverName/index.php/Home/Blog/archive/11/2013

输出的结果就变成了:

year=11&month=2013

显然就有问题了,所以不能随意调整参数在URL中的传递顺序,要确保和你的操作方法定义顺序一致。
可以看到,这种参数绑定的效果有点类似于简单的规则路由。
按变量顺序绑定的方式目前仅对PATHINFO地址有效,所以下面的URL访问参数绑定会失效:

http://serverName/index.php?c=Blog&a=read&id=5
http://serverName/index.php?c=Blog&a=archive&year=2013&month=11

但是,兼容模式URL地址访问依然有效:

http://serverName/index.php?s=/Home/Blog/read/5
http://serverName/index.php?s=/Home/Blog/archive/2013/11

如果你的操作方法定义都不带任何参数或者不希望使用该功能的话,可以关闭参数绑定功能:

'URL_PARAMS_BIND'       =>  false

ThinkPHP3.2.3空操作和空控制器

我们讲解下ThinkPHP控制器的两个实用特性空操作和空控制器,通常可以用来实现一些错误页面和URL优化。

空操作

空操作是指系统在找不到请求的操作方法的时候,会定位到当前控制器的空操作(_empty)方法来执行
例如,下面我们用空操作功能来实现一个城市切换的功能。 我们只需要给CityController类定义一个 _empty方法

<?php
namespace Home\Controller;
use Think\Controller;
class CityController extends Controller{
    public function _empty($name){
        //把所有城市的操作解析到city方法
        $this->city($name);
    }
    //注意 city方法 本身是 protected 方法
    protected function city($name){
        //和$name这个城市相关的处理
         echo '当前城市' . $name;
    }
}

接下来,我们就可以在浏览器里面输入

http://serverName/index.php/Home/City/beijing/
http://serverName/index.php/Home/City/shanghai/
http://serverName/index.php/Home/City/shenzhen/

由于City控制器并没有定义beijing、shanghai或者shenzhen操作方法,因此系统会定位到空操作方法 _empty中去解析,_empty方法的参数就是当前URL里面的操作名,因此会看到依次输出的结果是:

当前城市:beijing
当前城市:shanghai
当前城市:shenzhen

注意:空操作方法仅在你的控制器类继承系统的Think\Controller类才有效。

空控制器

空控制器的概念是指当系统找不到请求的控制器名称的时候,系统会尝试定位空控制器(EmptyController)。

现在我们把前面的需求进一步,把URL由原来的 http://serverName/index.php/Home/City/shanghai/
变成
http://serverName/index.php/Home/shanghai/

这样更加简单的方式,如果按照传统的模式,我们必须给每个城市定义一个控制器类,然后在每个控制器类的index方法里面进行处理。可是如果使用空控制器功能,这个问题就可以迎刃而解了。
我们可以给项目定义一个EmptyController类

<?php
namespace Home\Controller;
use Think\Controller;
class EmptyController extends Controller{
    public function index(){
        //根据当前控制器名来判断要执行那个城市的操作
        $cityName = CONTROLLER_NAME;
        $this->city($cityName);
    }
    //注意 city方法 本身是 protected 方法
    protected function city($name){
        //和$name这个城市相关的处理
         echo '当前城市' . $name;
    }
}

接下来,我们就可以在浏览器里面输入

http://serverName/index.php/Home/beijing/
http://serverName/index.php/Home/shanghai/
http://serverName/index.php/Home/shenzhen/

由于系统并不存在beijing、shanghai或者shenzhen控制器,因此会定位到空控制器(EmptyController)去执行,会看到依次输出的结果是:

当前城市:beijing
当前城市:shanghai
当前城市:shenzhen

空控制器和空操作还可以同时使用,用以完成更加复杂的操作。

ThinkPHP3.2.3初始化、前置和后置操作

我们讲解了如何在ThinkPHP控制器的操作方法调用之前或者之后做一些额外的操作,涉及到的知识点包括初始化操作、前置和后置操作

初始化操作

如果要在控制器的任何操作方法之前都执行某个方法的话,可以使用下面的方式:

namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller{
      // 初始化方法
      public function _initialize(){
          echo 'initialize<br/>';
      }
      public function index(){
          echo 'index';
      }
 
      public function hello(){
          echo 'hello';
      }
}

如果我们访问 http://serverName/index.php/Home/index/index
结果会输出

initialize
index

如果我们访问 http://serverName/index.php/Home/index/hello
结果会输出

initialize
hello

可以看出,无论是执行index操作还是hello操作 都会首先执行**_initialize操作方法**。
如果把**_initialize操作方法定义到一个公共的控制器类里面的话,那么所有的控制器操作方法之前都会执行。**

前置和后置操作

_initialize方法是调用所有操作方法之前都会执行,前置和后置操作则是针对某个特定的操作方法而言。
如果当前访问的操作是存在(必须是实际在控制器中定义过)的,系统会检测当前操作是否具有前置和后置操作,如果存在就会按照顺序执行,前置和后置操作的方法名是在要执行的方法前面加 _before_和_after_,例如:

namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller{
      //前置操作方法
      public function _before_index(){
          echo 'before<br/>';
      }
      public function index(){
          echo 'index<br/>';
      }
      //后置操作方法
      public function _after_index(){
          echo 'after';
      }
}

如果我们访问 http://serverName/index.php
结果会输出

before
index
after

对于任何操作方法我们都可以按照这样的规则来定义前置和后置方法。
如果在操作方法里面使用了exit或者error方法的话 有可能不会再执行后置方法了
,例如:

namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller{
      //前置操作方法
      public function _before_index(){
          echo 'before<br/>';
      }
      public function index(){
          echo 'index<br/>';
          exit;
      }
      //后置操作方法
      public function _after_index(){
          echo 'after';
      }
}

如果我们再次访问结果会输出

before
index

除了初始化、前置和后置操作之外,我们还可以在控制器以外的地方对操作方法进行扩展,这个会在下面行为扩展部分描述。

ThinkPHP3.2.3页面跳转和重定向

页面跳转

系统的Think\Controller类内置了两个页面跳转方法error和success,分别用于错误(提示)跳转和成功(提示)跳转。两个方法都会输出一个提示信息页面,然后自动跳转到指定的地址。下面是一个简单的例子:

$New = M('New'); //实例化New对象
$result = $New->add($data); 
if($result){
    // 成功后跳转到新闻列表页面
    $this->success('新增成功,即将返回列表页面', '/New/index');
} else {
    // 错误页面的默认跳转页面是返回前一页,通常不需要设置
    $this->error('新增失败');
}

success和error方法有三个参数,分别是提示信息、跳转地址和跳转页面等待时间(秒),除了第一个参数外其他都是可选的

提示信息:成功或者错误信息字符串。
跳转地址:页面跳转地址是可选的,success方法的默认跳转地址是$_SERVER[“HTTP_REFERER”],error方法的默认跳转地址是javascript:history.back(-1);。
等待时间:默认的等待时间success方法是1秒,error方法是3秒。

success和error方法都可以对应的模板,默认两个方法对应的模板是框架自带的跳转模板dispatch_jump.tpl

//默认错误跳转对应的模板文件
‘TMPL_ACTION_ERROR’ => THINK_PATH . ‘Tpl/dispatch_jump.tpl’,
//默认成功跳转对应的模板文件
‘TMPL_ACTION_SUCCESS’ =>THINK_PATH . ‘Tpl/dispatch_jump.tpl’,

在这里插入图片描述
你可以重新定义跳转模板,通常建议直接放到项目目录下面(下面采用公共模块的模板作为项目统一的跳转模板):

//默认错误跳转对应的模板文件
'TMPL_ACTION_ERROR' => 'Common@Public/error',
//默认成功跳转对应的模板文件
'TMPL_ACTION_SUCCESS' => 'Common@Public/success',

模板文件可以使用模板标签,并且可以使用下面的模板变量:

**变量	含义**
$message	页面成功提示信息
$error	页面错误提示信息
$waitSecond	跳转等待时间 单位为秒
$jumpUrl	跳转页面地址

重定向

如果不需要提示页面,ThinkPHP还可以实现直接重定向操作**,Think\Controller类提供了redirect方法实现页面的重定向功能。**
重定向到操作

redirect('重定向操作地址(一般为[控制器/操作])','参数(字符串或者数组)','重定向等待时间(秒)','重定向提示信息')

例如:

$New = M('New'); //实例化New对象
$result = $New->add($data); 
if($result){
    // 停留5秒后跳转到New模块的category操作,并且显示页面跳转中字样
    $this->redirect('New/category', 'cate_id=2&status=1', 5,'页面跳转中...');
} else {
    // 错误页面
    $this->redirect('New/error');
}

可以传入参数和设置重定向的等待时间,甚至给出等待的提示信息:
注意:重定向后会改变当前的URL地址。

重定向到URL

如果你仅仅是想重定向要一个指定的URL地址,而不是到控制器的操作方法,可以直接使用redirect函数重定向,例如:

$New = M('New'); //实例化New对象
$result = $New->add($data); 
if($result){
    //重定向到指定的URL地址
    redirect('/New/category/cate_id/2', 5, '页面跳转中...');
}

redirect函数的第一个参数是要跳转的实际URL地址

ThinkPHP3.2.3页面请求和AJAX

判断请求类型

在很多情况下面,我们需要判断当前操作的请求类型是GET 、POST 、PUT或 DELETE,一方面可以针对请求类型作出不同的逻辑处理,另外一方面有些情况下面需要验证安全性,过滤不安全的请求。
系统内置了一些常量用于判断请求类型,包括:

**常量	说明**
IS_GET	判断是否是GET方式提交
IS_POST	判断是否是POST方式提交
IS_PUT	判断是否是PUT方式提交
IS_DELETE	判断是否是DELETE方式提交
IS_AJAX	判断是否是AJAX提交
REQUEST_METHOD	当前提交类型

使用举例如下:

class UserController extends Controller{
     public function update(){
         if (IS_POST){
             $User = M('User');
             $User->create();
             $User->save();
             $this->success('保存完成');
         }else{
             $this->error('非法请求');
         }
     }
}

个别情况下判断AJAX请求的时候,你可能需要在表单里面添加一个隐藏域,告诉后台属于ajax方式提交,默认的隐藏域名称是ajax(可以通过VAR_AJAX_SUBMIT配置),如果是JQUERY类库的话,则无需添加任何隐藏域即可自动判断。

AJAX返回

ThinkPHP可以很好的支持AJAX请求,系统的\Think\Controller类提供了ajaxReturn方法用于AJAX调用后返回数据给客户端。并且支持JSON、JSONP、XML和EVAL四种方式给客户端接受数据,并且支持配置其他方式的数据格式返回。
ajaxReturn方法调用示例:

$data = 'ok';
$this->ajaxReturn($data);

支持返回数组数据:

$data['status']  = 1;
$data['content'] = 'content';
$this->ajaxReturn($data);

默认配置采用JSON格式返回数据(通过配置DEFAULT_AJAX_RETURN进行设置),我们可以指定格式返回,例如:
// 指定XML格式返回数据

$data['status']  = 1;
$data['content'] = 'content';
$this->ajaxReturn($data,'xml');

返回数据data可以支持字符串、数字和数组、对象,返回客户端的时候根据不同的返回格式进行编码后传输。如果是JSON/JSONP格式,会自动编码成JSON字符串,如果是XML方式,会自动编码成XML字符串,如果是EVAL方式的话,只会输出字符串data数据。
JSON和JSONP虽然只有一个字母的差别,但其实他们根本不是一回事儿:JSON是一种数据交换格式,而JSONP是一种非官方跨域数据交互协议。一个是描述信息的格式,一个是信息传递的约定方法
默认的JSONP格式返回的处理方法是jsonpReturn,如果你采用不同的方法,可以设置:

'DEFAULT_JSONP_HANDLER' =>  'myJsonpReturn', // 默认JSONP格式返回的处理方法

或者直接在页面中用callback参数来指定

ThinkPHP3.2.3伪静态

URL伪静态通常是为了满足更好的SEO效果,ThinkPHP支持伪静态URL设置,可以通过设置URL_HTML_SUFFIX参数随意在URL的最后增加你想要的静态后缀,而不会影响当前操作的正常执行。

单个URL后缀

默认情况下,伪静态的设置为html,因此下面的URL访问是等效的:

http://serverName/Home/Blog/index 
http://serverName/Home/Blog/index.html

但后者更具有静态页面的URL特征,并且不会影响原来参数的使用。
但如果我们访问

http://serverName/Home/Blog/index.xml

则会提示出错。
在这里插入图片描述
除非我们设置了:

'URL_HTML_SUFFIX'=>'xml'

全后缀支持

如果我们设置伪静态后缀为空,则可以支持所有的静态后缀访问,并且会记录当前的伪静态后缀到常量 EXT ,但不会影响正常的页面访问。

'URL_HTML_SUFFIX'=>''

设置后,下面的URL访问都有效:

http://serverName/Home/blog/index.html
http://serverName/Home/blog/index.shtml
http://serverName/Home/blog/index.xml
http://serverName/Home/blog/index.pdf

可以通过常量 EXT 判断当前访问的后缀,例如:

if('pdf'==__EXT__){
  // 输出PDF文档
}elseif('xml'==__EXT__){
  // 输出XML格式文档
}

多个后缀支持

如果希望仅支持设置的多个伪静态后缀访问,可以设置如下:

// 多个伪静态后缀设置 用|分割
'URL_HTML_SUFFIX' => 'html|shtml|xml'

那么,当访问 http://serverName/Home/blog/index.pdf 的时候会报系统错误。

禁止访问后缀

可以设置禁止访问的URL后缀,例如:

'URL_DENY_SUFFIX' => 'pdf|ico|png|gif|jpg', // URL禁止访问的后缀设置

如果访问 http://serverName/Home/blog/index.pdf 就会直接返回404错误。
注意:
URL_DENY_SUFFIX的优先级比URL_HTML_SUFFIX要高。

ThinkPHP3.2.3操作绑定到类

如果你的应用规模比较大,每个操作方法彼此相对独立,那么就可以尝试下操作绑定到类的功能。
定义
系统提供了把每个操作方法定位到一个类的功能,可以让你的开发工作更细化,可以设置参数ACTION_BIND_CLASS,例如:

'ACTION_BIND_CLASS'    =>    True,

设置后,我们的控制器定义有所改变,以URL访问为http://serverName/Home/Index/index为例,原来的控制器文件定义位置为

Application/Home/Controller/IndexController.class.php

控制器类的定义如下:

namespace Home\Controller;
use Think\Controller;
class IndexController extends Controller{
    public function index(){
        echo '执行Index控制器的index操作';
    }
}

可以看到,实际上我们调用的是 Home\Controller\IndexController 类的index方法。
设置操作绑定到类以后,控制器文件位置改为:

Application/Home/Controller/Index/index.class.php

复制
控制器类的定义如下:

namespace Home\Controller\Index;
use Think\Controller;
class index extends Controller{
    public function run(){
        echo '执行Index控制器的index操作';
    }
}

现在,我们调用的其实是 Home\Controller\Index\index 类的run方法。
注意操作方法类的命名空间比之前要多了一个控制器名称,这个地方很容易忽略。
run方法依旧可以支持传入参数和进行Action参数绑定操作。

namespace Home\Controller\Index;
use Think\Controller;
class index extends Controller{
    public function run($name=''){
        echo 'Hello,'.$name.'!';
    }
}

我们访问

http://serverName/Home/Index/index/name/thinkphp

复制
可以看到输出结果为:

Hello,thinkphp!

前置和后置操作

当设置操作方法绑定到类后,前置和后置操作的定义有所改变,只需要在类里面定义**_before_run和_after_run方法**即可,例如:

namespace Home\Controller\Index;
use Think\Controller;
class index extends Controller{
    public function _before_run(){
        echo 'before_'.ACTION_NAME;
    }
 
    public function run(){
        echo '执行Index控制器的index操作';
    }
 
    public function _after_run(){
        echo 'after_'.ACTION_NAME;
    }
}

空控制器

操作方法绑定到类后,一样可以支持空控制器,我们可以创建 Application/Home/Controller/_empty目录,即表示如果找不到当前的控制器的话,会到_empty控制器目录下面定位操作方法。
例如,我们访问了URL地址 http://serverName/Home/Test/index,但并不存在 Application/Home/Controller/Test目录,但是有定义 Application/Home/Controller/_empty目录。
并且我们有定义:

Application/Home/Controller/_empty/index.class.php

控制器定义如下:

namespace Home\Controller\_empty;
use Think\Controller;
class index extends Controller{
    public function run(){
        echo '执行'CONTROLLER_NAME.'控制器的'.ACTION_NAME.'操作';
    }
}

访问http://serverName/Home/Test/index后 输出结果显示:

执行Test控制器的index操作

空操作

操作绑定到类后,我们依然可以实现空操作方法,我们只要定义一个 Home\Controller\Index_empty类,就可以支持Index控制器的空操作访问,例如: 控制器定义如下:

namespace Home\Controller\Index;
use Think\Controller;
class _empty extends Controller{
    public function run(){
        echo '执行Index控制器的'.ACTION_NAME.'操作';
    }
}

当我们访问http://serverName/Home/Index/test后 输出结果显示:

执行Index控制器的test操作

ThinkPHP3.2.3多层控制器

ThinkPHP支持多层业务控制器的支持,给中大型应用提供了方便。

定义多层控制器

我们通常所了解的控制器其实是Controller控制器类,而且大多数也是继承了核心的Think\Controller类,由于该类控制器是通过URL访问请求后调用的,因此也称之为访问控制器,事实上,ThinkPHP可以支持更多的控制器分层,多层控制器的定义完全取决于项目的需求,例如我们可以分为业务控制器和事件控制器

Home\Controller\UserController //用于用户的业务逻辑控制和调度
Home\Event\UserEvent //用于用户的事件响应操作
├─Controller 访问控制器
│  ├─UserController.class.php │  ...
├─Event 事件控制器
│  ├─UserEvent.class.php  │  ...

一个标准的访问控制器定义如下:

namespace Home\Controller;
class UserController extend Think\Controller {
    // 默认操作方法
    public function index(){
        //...
    }
 
    // 用户注册操作方法
    public function register(){
        //...
    }
}

注:访问控制器的名称并非一定是Controller,而是通过DEFAULT_C_LAYER设置的,默认设置是Controller。
访问控制器负责外部的交互响应,通过URL请求调用,例如:

http://serverName/Home/User/index
http://serverName/Home/User/register

而事件控制器负责内部的事件响应,并且只能在内部调用,所以是和外部隔离的。
确切的说,所有访问控制器之外的分层控制器都只能内部实例化调用。

namespace Home\Event;
class UserEvent {
    // 用户登录事件
    public function login(){
        echo 'login event';
    }
 
    // 用户登出事件
    public function logout(){
        echo 'logout event';
    }
}

如果是定义其他的控制器层,则不一定必须要继承系统的Controller类或其子类,通常需要输出模版的时候才需要继承Controller类。

调用多层控制器

访问控制器是通过URL请求调用,访问控制器之外的分层控制器都只能内部调用,调用多层控制器可以通过两种方式
直接实例化

namespace Home\Controller;
class UserController extend Think\Controller {
    // 默认操作方法
    public function index(){
        // 触发事件
        $event = new \Home\Event\UserEvent();
        $event->login();
    }
 
}

A函数实例化

namespace Home\Controller;
class UserController extend Think\Controller {
    // 默认操作方法
    public function index(){
        // 触发事件
        $event = A('User','Event');
        $event->login();
        // 或者直接使用
        // R('User/login','','Event');
    }
}

Widget实例

Widget类的实现可以作为分层控制器的另外一个典型实例。
举个例子,我们在页面中实现一个分类菜单的Widget,首先我们要定义一个Widget控制器层 MenuWidget,如下:

namespace Home\Widget;
class MenuWidget extends Think\Controller {
    public function index(){
        echo 'menuWidget';
    }
 }

类文件位于 Home/Widget/MenuWidget.class.php。
然后,我们在需要显示分类菜单的模版中通过W方法调用这个Widget。

{~W('Menu/index')}

执行后的输出结果是: menuWidget
如果需要在调用Widget的时候传入参数,可以这样定义:

namespace Home\Widget;
class MenuWidget extends Think\Controller {
    public function index($id,$name){
        echo $id.':'.$name;
    }
 }

在需要显示分类菜单的模版中添加如下的Widget调用代码如下:

{~W('Menu/index',array(5,'thinkphp'))}

则会输出 5:thinkphp
来一个复杂一点的例子:

namespace Home\Widget;
class MenuWidget extends Think\Controller {
    public function index(){
        $menu = M('Cate')->getField('id,title');
        $this->assign('menu',$menu);
        $this->display('Widget/menu');
    }
 }

CateWiget类渲染了一个模版文件 Home/View/Widget/menu.html,
在menu.html模版文件中的用法:

<foreach name="menu" item="title">
{$key}:{$title}
</foreach>

ThinkPHP3.2.3自动验证

ThinkPHP的模型可以支持数据自动验证功能,用于验证提交到数据库的数据的有效性和安全性。
该自动验证为后端验证,并非前端验证。
可以支持两种方式的自动验证,包括静态验证和动态验证。

静态验证

静态验证是指把验证的规则直接定义在模型类里面,也就是说,这种验证必须有自定义的模型类存在,假设我们的用户注册表单如下:

<form method="post" action="/user/register">
    <input type="text" name="name" >
    <input type="text" name="email" >
    <input type="password" name="password" >
    <input type="password" name="repassword" >
    <img height="42" src="/user/verify" >
    <input name="verify" type="text" maxlength="4">
</form>

我们需要对用户注册表单进行一些相关的验证操作,包括:

用户名必须;
验证码用户名是否已经存在;
如果输入邮箱则验证邮箱格式是否正确;
验证密码是否为指定长度; 确认密码是否和密码一致;

按照上述的验证规则,我们定义好自动验证规则,为UserModel类添加 $_validate 属性定义如下:

namespace Home\Model;
use Think\Model;
class UserModel extends Model{
   protected $_validate = array(
     array('name','require','用户名必须!'), // 用户名必须
     array('name','','帐号名称已经存在!',1,'unique',1), // 验证用户名是否已经存在
     array('email','email','Email格式错误!',2), // 如果输入则验证Email格式是否正确
     array('password','6,30','密码长度不正确',0,'length'), // 验证密码是否在指定长度范围
     array('repassword','password','确认密码不一致',0,'confirm'), // 验证确认密码是否和密码一致     
   );
}

定义好自动验证规则后,我们就可以在控制器使用create方法进行自动验证了。

$User = D("User"); // 实例化User对象
if (!$User->create()){
     // 如果创建失败 表示验证没有通过 输出错误提示信息
     exit($User->getError());
}else{
     // 验证通过 可以进行其他数据操作
     $User->add();
}

在进行自动验证的时候,系统会对定义好的验证规则进行依次验证。对于某个字段也可以进行多次规则验证,如果某一条验证规则没有通过,则会报错,getError方法返回的错误信息(字符串)就是对应字段的验证规则里面的错误提示信息。

验证规则

验证规则的定义格式为:

array(验证字段1,验证规则,错误提示,[验证条件,附加规则,验证时间]),

验证字段 (必须)
需要验证的表单字段名称,这个字段不一定是数据库字段,也可以是表单的一些辅助字段,例如确认密码和验证码等等。有个别验证规则和字段无关的情况下,验证字段是可以随意设置的,例如expire有效期规则是和表单字段无关的。如果定义了字段映射的话,这里的验证字段名称应该是实际的数据表字段而不是表单字段。

验证规则 (必须)
要进行验证的规则,需要结合附加规则,如果在使用正则验证的附加规则情况下,系统还内置了一些常用正则验证的规则,可以直接作为验证规则使用,包括:require
字段必须、email 邮箱、url URL地址、currency 货币、number 数字。

提示信息 (必须)
用于验证失败后的提示信息定义

验证条件 (可选)
包含下面几种情况:
self::EXISTS_VALIDATE 或者0 存在字段就验证(默认)
self::MUST_VALIDATE 或者1 必须验证
self::VALUE_VALIDATE或者2 值不为空的时候验证

附加规则 (可选)
配合验证规则使用,包括下面一些规则:

   **规则	说明**
> regex	正则验证,定义的验证规则是一个正则表达式(默认) function	函数验证,定义的验证规则是一个函数名
> callback	方法验证,定义的验证规则是当前模型类的一个方法 confirm	验证表单中的两个字段是否相同,定义的验证规则是一个字段名
> equal	验证是否等于某个值,该值由前面的验证规则定义
> notequal	验证是否不等于某个值,该值由前面的验证规则定义(3.1.2版本新增)
> in	验证是否在某个范围内,定义的验证规则可以是一个数组或者逗号分割的字符串
> notin	验证是否不在某个范围内,定义的验证规则可以是一个数组或者逗号分割的字符串(3.1.2版本新增)
> length	验证长度,定义的验证规则可以是一个数字(表示固定长度)或者数字范围(例如3,12 表示长度从312的范围)
> between	验证范围,定义的验证规则表示范围,可以使用字符串或者数组,例如1,31或者array(1,31)
> notbetween	验证不在某个范围,定义的验证规则表示范围,可以使用字符串或者数组(3.1.2版本新增)
> expire	验证是否在有效期,定义的验证规则表示时间范围,可以到时间,例如可以使用 2012-1-15,2013-1-15
> 表示当前提交有效期在2012-1-152013-1-15之间,也可以使用时间戳定义
> ip_allow	验证IP是否允许,定义的验证规则表示允许的IP地址列表,用逗号分隔,例如201.12.2.5,201.12.2.6
> ip_deny	验证IP是否禁止,定义的验证规则表示禁止的ip地址列表,用逗号分隔,例如201.12.2.5,201.12.2.6
> unique	验证是否唯一,系统会根据字段目前的值查询数据库来判断是否存在相同的值,当表单数据中包含主键字段时unique不可用于判断主键字段本身
> 验证时间(可选) self::MODEL_INSERT或者1新增数据时候验证 self::MODEL_UPDATE或者2编辑数据时候验证
> self::MODEL_BOTH或者3全部情况下验证(默认)

这里的验证时间需要注意,并非只有这三种情况,你可以根据业务需要增加其他的验证时间。

动态验证

如果采用动态验证的方式,因为不依赖模型类的定义,就比较灵活,可以根据不同的需要,在操作同一个模型的时候使用不同的验证规则,而且可以直接使用M函数操作模型类,例如上面的静态验证方式可以改为:

$rules = array(
     array('name','require','用户名必须!'), // 用户名必须
     array('name','','帐号名称已经存在!',1,'unique',1), // 验证用户名是否已经存在
     array('email','email','Email格式错误!',2), // 如果输入则验证Email格式是否正确
     array('password','6,30','密码长度不正确',0,'length'), // 验证密码是否在指定长度范围
     array('repassword','password','确认密码不一致',0,'confirm'), // 验证确认密码是否和密码一致  
);
 
$User = M("User"); // 实例化User对象
if (!$User->validate($rules)->create()){
     // 如果创建失败 表示验证没有通过 输出错误提示信息
     exit($User->getError());
}else{
     // 验证通过 可以进行其他数据操作
     $User->add();
}

ThinkPHP3.2.3自动完成

自动完成是ThinkPHP提供用来完成数据自动处理和过滤的方法,使用create方法创建数据对象的时候会自动完成数据处理。

规则定义

自动完成通常用来完成默认字段写入,安全字段过滤以及业务逻辑的自动处理等,和自动验证的定义方式类似,自动完成的定义也支持静态定义和动态定义两种方式

静态方式:在模型类里面通过$_auto属性定义处理规则。
动态方式:使用模型类的auto方法动态创建自动处理规则。

两种方式的定义规则都采用:

array(
     array(完成字段1,完成规则,[完成条件,附加规则]),
     array(完成字段2,完成规则,[完成条件,附加规则]),
     ......
);

说明

完成字段(必须) 需要进行处理的数据表实际字段名称。
完成规则(必须) 需要处理的规则,配合附加规则完成。
完成时间(可选)
设置自动完成的时间,包括:

设置 说明
self::MODEL_INSERT或者1 新增数据的时候处理(默认)
self::MODEL_UPDATE或者2 更新数据的时候处理 self::MODEL_BOTH或者3 所有情况都进行处理
附加规则(可选)
设置自动完成的附加规则,包括:
规则 说明
function 使用函数,表示填充的内容是一个函数名 callback 回调方法 ,表示填充的内容是一个当前模型的方法 field 用其它字段填充,表示填充的内容是一个其他字段的值 string 字符串(默认方式)
ignore 为空则忽略

预先在模型类里面定义好自动完成的规则,我们称之为静态定义。例如,我们在模型类定义_auto属性:

namespace Home\Model;
 
use Think\Model;
 
class UserModel extends Model{
     protected $_auto = array ( 
         array('status','1'),  // 新增的时候把status字段设置为1
         array('password','md5',3,'function') , // 对password字段在新增和编辑的时候使md5函数处理
         array('name','getName',3,'callback'), // 对name字段在新增和编辑的时候回调getName方法
         array('update_time','time',2,'function'), // 对update_time字段在更新的时候写入当前时间戳
     );
}

然后,就可以在使用create方法创建数据对象的时候自动处理:

$User = D("User"); // 实例化User对象
if (!$User->create()){ // 创建数据对象
     // 如果创建失败 表示验证没有通过 输出错误提示信息
     exit($User->getError());
}else{
     // 验证通过 写入新增数据
     $User->add();
}

如果你没有定义任何自动验证规则的话,则不需要判断create方法的返回值:

$User = D("User"); // 实例化User对象
$User->create(); // 生成数据对象
$User->add(); // 新增用户数据

create方法默认情况下是根据表单提交的post数据生成数据对象,我们也可以根据其他的数据源来生成数据对象,你也可以明确指定当前创建的数据对象自动处理的时间是新增还是编辑数据,例如:

$User = D("User"); // 实例化User对象
$userData = getUserData(); // 通过方法获取用户数据
$User->create($userData,2); // 根据userData数据创建数据对象,并指定为更新数据
$User->add();

create方法的第二个参数就用于指定自动完成规则中的完成时间,也就是说create方法的自动处理规则只会处理符合完成时间的自动完成规则。 create方法在创建数据的时候,已经自动过滤了非数据表字段数据信息,因此不需要担心表单会提交其他的非法字段信息而导致数据对象写入出错,甚至还可以自动过滤不希望用户在表单提交的字段信息(详见字段合法性过滤)。
3.1.2版本开始新增了ignore完成规则,这一规则表示某个字段如果留空的话则忽略,通常可用于修改用户资料时候密码的输入,定义如下:

array('password','',2,'ignore')

表示password字段编辑的时候留空则忽略。

动态完成

除了静态定义之外,我们也可以采用动态设置自动完成规则的方式来解决不同的处理规则。

$rules = array ( 
    array('status','1'),  // 新增的时候把status字段设置为1
    array('password','md5',3,'function') , // 对password字段在新增和编辑的时候使md5函数处理
    array('update_time','time',2,'function'), // 对update_time字段在更新的时候写入当前时间戳
);
$User = M('User');
$User->auto($rules)->create();
$User->add();

修改数据对象
在使用create方法创建好数据对象之后,此时的数据对象保存在内存中,因此仍然可以操作数据对象,包括修改或者增加数据对象的值,例如:

$User = D("User"); // 实例化User对象
$User->create(); // 生成数据对象
$User->status = 2; // 修改数据对象的status属性
$User->register_time = NOW_TIME; // 增加register_time属性
$User->add(); // 新增用户数据

一旦调用了add方法(或者save方法),创建在内存中的数据对象就会失效,如果希望创建好的数据对象在后面的数据处理中再次调用,可以保存数据对象先,例如:

$User = D("User"); // 实例化User对象
$data = $User->create(); // 保存生成的数据对象
$User->add();

不过要记得,如果你修改了内存中的数据对象并不会自动更新保存的数据对象,因此下面的用法是错误的:

$User = D("User"); // 实例化User对象
$data = $User->create(); // 保存生成的数据对象
$User->status = 2; // 修改数据对象的status属性
$User->register_time = NOW_TIME; // 增加register_time属性
$User->add($data);

上面的代码我们修改了数据对象,但是仍然写入的是之前保存的数据对象,因此对数据对象的更改操作将会无效。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值