欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

Laravel框架实战:增加表单专用Model

shiping1 的头像
随着进一步使用Laravel框架,感觉也需要对MVC模式进行重新的梳理。在原有的代码中,网页上的表单提交后,在控制器中是通过:
$input = Input::All();

$input = Input:: All ( ) ;

来提取,取得的内容为表单的name => value键值对,随后我们再在控制器中编写相应的校验规则来对这些参数的合法性进行检验,并根据检验结果来控制页面的跳转。然后随着这样的代码越写越多,我们也就发现了这样写法可能会带来的问题:

  • 当表单发生变化的时候,必须要找到表单action所对应的控制器的对应代码来进行修改;
  • 需要通过使用 Input::All() 数组来获取内容,这使得代码中势必要出现大量的Magic string,不利于系统未来可能发生的重构;
  • 针对表单->实体模型的转换,每种转换都需要在Controller中编写,在修改转换逻辑的时候会造成不便(例如,用户注册的表单并不是直接和数据库中的用户表对应的,用户注册表单的数据需要一定处理后才能被存入数据表)
  • ……

这些主要就是耦合度的问题,那么如何解决呢?

MVC模式中,M Model,是数据模型,数据模型是我们在C Controller中进行页面控制的依据,也是V View在前端进行显示的基础,数据模型能够提供 数据库->数据结构(对象) 的转换,当然也应该能够提供 表单->数据结构(对象) 的转换,有了这样的转换,我们就可以把表单的数据组织起来。

Laravel自己提供了数据库->数据结构(对象)的模型转换,有Database和ORM,但是没有提供表单模型的转换,所以我们需要自己来进行扩展。

下面的代码是我写的表单模型的基类:

abstract class BaseFormModel
{

  private $_validator;

  protected function init($input, $rule = array())
  {
    $this->_validator = Validator::make($input, $rule);

    $formKey = array_keys(get_class_vars(get_class($this)));
    // 遍历表单键值 并赋予类成员
    foreach ($formKey as $value) 
    {
      if(isset($input[$value]))
      {
        $this->$value = $input[$value];
      }
    }
  }

  public function validator()
  {
    return $_validator;
  }

  public function isValid()
  {
    return !$_validator->fails();
  }

}

abstract class BaseFormModel

{

     private $_validator ;

     protected function init ( $input , $rule = array ( ) )

     {

         $this -> _validator = Validator:: make ( $input , $rule ) ;

         $formKey = array_keys ( get_class_vars ( get_class ( $this ) ) ) ;

         // 遍历表单键值 并赋予类成员

         foreach ( $formKey as $value )

         {

             if ( isset ( $input [ $value ] ) )

             {

                 $this -> $value = $input [ $value ] ;

             }

         }

     }

     public function validator ( )

     {

         return $_validator ;

     }

     public function isValid ( )

     {

         return ! $_validator -> fails ( ) ;

     }

}

可以看到,这个类目前提供了三种方法,第一个是初始化,第二个是返回一个校验器,第三个用来返回目前这个表单模型的内容是否符合要求。

初始化方法接收了两个参数,第一个就是从表单收集过来的数据,也就是之前提到的Input:All()数组,第二个是验证规则,用于产生一个校验器来校验表单内容是否合法。在接收这两个参数过后,初始化方法会先产生表单模型的校验器,随后检查类中所有的成员属性,并赋予与之对应的Input::All()数组中的值。在这里,表单模型将只接收成员属性表中的输入内容,如果Input::All()中存在无关内容,那么表单模型是不会接收的。

下面以注册表单为例子来解释这个表单模型该如何使用,首先是注册表单:

<form role="form" method="post" id="signup_form" action="{{{ URL::action('UserController@signup') }}}">
      <div class="form-group">
         <label for="email">{{{ Lang::get('site.email') }}}</label><input type="email" class="form-control" id="email" name="email"/>
      </div>
      <div class="form-group">
         <label for="name">{{{ Lang::get('site.username') }}}</label><input type="text" class="form-control" id="name" name="name"/>
      </div>
      <div class="form-group">
         <label for="password">{{{ Lang::get('site.password') }}}</label><input type="password" class="form-control" id="password" name="password"/>
      </div>
      <div class="form-group">
         <label for="passwordConfirm">{{{ Lang::get('site.password_confirm') }}}</label><input type="password" class="form-control" id="passwordConfirm" name="passwordConfirm"/>
      </div>
      <div class="form-group">
        <button type="submit" class="btn btn-lg btn-primary btn-block">{{{ Lang::get('site.signup') }}}</button>
        <button type="button" class="btn btn-default btn-lg btn-block" id="signin_button">{{{ Lang::get('site.signin') }}}</button>
      </div>
    </form>

<form role = "form" method = "post" id = "signup_form" action = "{{{ URL::action('UserController@signup') }}}" >

       <div class = "form-group" >

         <label for = "email" > {{{ Lang::get('site.email') }}} </label> <input type ="email" class = "form-control" id = "email" name = "email" />

       </div>

       <div class = "form-group" >

         <label for = "name" > {{{ Lang::get('site.username') }}} </label> <input type ="text" class = "form-control" id = "name" name = "name" />

       </div>

       <div class = "form-group" >

         <label for = "password" > {{{ Lang::get('site.password') }}} </label> <inputtype = "password" class = "form-control" id = "password" name = "password" />

       </div>

       <div class = "form-group" >

         <label for = "passwordConfirm" > {{{ Lang::get('site.password_confirm') }}}</label> <input type = "password" class = "form-control" id = "passwordConfirm"name = "passwordConfirm" />

       </div>

       <div class = "form-group" >

         <button type = "submit" class = "btn btn-lg btn-primary btn-block" > {{{ Lang::get('site.signup') }}} </button>

         <button type = "button" class = "btn btn-default btn-lg btn-block" id ="signin_button" > {{{ Lang::get('site.signin') }}} </button>

       </div>

     </form>

注册表单包含了四项内容,email、name、password、passwordConfrim,因此我们要写的注册表单模型就也要包含这四项内容,代码如下:

class SignupForm extends BaseFormModel
{
  public $email;
  public $name;
  public $password;
  public $passwordConfirm;

  public function __construct($input)
  {
    $rule = array(
      'email' 			=> 'required|email|unique:tp_users',
      'name' 				=> 'required|min:6|unique:tp_users',
      'password' 			=> 'required|min:8',
      'passwordConfirm' 	=> 'required|same:password'
      );

    $this->init($input, $rule);
  }
}

class SignupForm extends BaseFormModel

{

     public $email ;

     public $name ;

     public $password ;

     public $passwordConfirm ;

     public function __construct ( $input )

     {

         $rule = array (

             'email'              = > 'required|email|unique:tp_users' ,

             'name'                  = > 'required|min:6|unique:tp_users' ,

             'password'              = > 'required|min:8' ,

             'passwordConfirm'      = > 'required|same:password'

             ) ;

         $this -> init ( $input , $rule ) ;

     }

}

非常简单,验证规则在构造函数中填写就可以了,校验规则的使用方法是由Laravel框架提供的,非常方便,随后将参数注入到初始化方法就算完成了,非常简单吧。

在使用的时候,只要像下面这样的代码即可:

public function signup()
  {
    // 对输入进行校验
    $signupForm = new SignupForm(Input::all());

    $notice = new Notice(Notice::danger, Lang::get('notice.signup_error'));

    if($signupForm->isValid())
    {
      // 创建用户账户
      $user = User::newUser($signupForm);
      // 发送确认邮件
      $toBeConfirmed = ToBeConfirmed::newSignupConfirm($user->id);

      $mailData = array(
              'userId' => $user->id,
              'checkCode' => $toBeConfirmed->check_code,
            );
      Mail::send('emails.welcome', $mailData, function($message) use($user)
      {
        $message->to($user->email)->subject(Lang::get('site.signup_email_subject'));
      });
      $notice = new Notice(Notice::success, 
        Lang::get('notice.signup_success', array('email' => $user->email)));
    }
    
    $this->MergeData(Lang::get('base.signup'));
    $this->MergeData($notice->getData());
    return View::make('common.notice', $this->data);
  }

     public function signup ( )

     {

         // 对输入进行校验

         $signupForm = new SignupForm ( Input:: all ( ) ) ;

         $notice = new Notice ( Notice:: danger , Lang:: get ( 'notice.signup_error' ) ) ;

         if ( $signupForm -> isValid ( ) )

         {

             // 创建用户账户

             $user = User:: newUser ( $signupForm ) ;

             // 发送确认邮件

             $toBeConfirmed = ToBeConfirmed:: newSignupConfirm ( $user -> id ) ;

             $mailData = array (

                             'userId' = > $user -> id ,

                             'checkCode' = > $toBeConfirmed -> check_code ,

                         ) ;

             Mail:: send ( 'emails.welcome' , $mailData , function ( $message ) use ($user )

             {

                 $message -> to ( $user -> email ) -> subject ( Lang:: get ('site.signup_email_subject' ) ) ;

             } ) ;

             $notice = new Notice ( Notice:: success ,

                 Lang:: get ( 'notice.signup_success' , array ( 'email' = > $user -> email )) ) ;

         }

        

         $this -> MergeData ( Lang:: get ( 'base.signup' ) ) ;

         $this -> MergeData ( $notice -> getData ( ) ) ;

         return View:: make ( 'common.notice' , $this -> data ) ;

     }

这之中,高亮的有一行我也想特别提一下,在使用表单模型后,表单模型->数据库模型的转换就可以通过自定义方法来实现了(并且可以进行类型检查,使用数组的话就比较麻烦),而无需在控制器中对数据库模型一一赋值后再创建,如果未来有模块还需要通过相同表单来创建用户,那么数据库模型的这个转换方法是可以复用的。

总之我们修改的原则就是,降低系统模块之间的耦合度,提高代码的复用程度。

来自  http://www.tuicool.com/articles/m6rmEr

普通分类: