developer.co.ua

Holy Copypasters
10.04.2008
Андрей Друченко

Валидация форм в CakePHP 1.2 0.12

В этой статье показывается пример организации валидации типовых форм (типа login / register ) с помощью новой схемы валидации в MVC фреймворке CakePHP 1.2
В тексте мало слов и много кода, некоторые куски которого можно просто скопировать (CSS) а в некоторые придется вникнуть, для лучшего понимания того как работает вся система валидации в связке Model-View-Controller.

Как мы уже знаем из документации, в Cake 1.2 значительно поменялся механизм валидации форм — делать сложную и комплексную валидацию стало проще и веселей.
Перед тем как мы начнем рассматривать примеры, сделаем предварительный файл стилей, специально для форм и для тех стандартных вещей в верстке, которые кейковские хелперы делают за нас.

Итак, вот приблизительный пример рабочего CSS

/* a special style for displaying forms */

form { border:1px solid #E3EEF7;float:left;}

div.input {padding:5px;border-bottom:1px solid #DAEBD3;
clear:both;float:left;min-width:430px;width:430px;}

/* for highlighting */
div.required { background-color:#EDFFE5;}
div.input label { display:blockwidth:10em;padding:2px;font-weight:bold;float:left;}

/* tips for field */
div.input .tip-message { max-width:305px;width:305px;clear:both;padding-left:125px;
padding-bottom:2px;font-size:90%;color:#999;display:block;}

/*error messages */
div.input div.error-message { max-width:305px;width:305px;clear:both;
padding-left:125px;padding-top:2px;font-size:90%;color:#990000;}

div.input_checkbox div.error-message { clear:both;padding-left:5px;
padding-top:2px;font-size:90%;color:#990000;}


/* default style for <input type="text" />, <textarea/> */

div.input input, div.input textarea {border:1px solid #9BA3A9;padding:2px;font-family:Arial, Verdana;
font-size:110%;min-width:300px;width:300px;float:left;}


div.input_checkbox {padding:5px 5px 5px 10.3em;border-bottom:1px solid #DAEBD3;
min-width:310px;width:310px;float:left;clear:both;
}


div.input .checkbox width:20pxheight:20px;display:block;float:right;} 

div.submit {padding:5px 5px 5px 10.3em;background-color:#FFF9BF;
min-width:310px;width:310px;float:left;clear:both;}

div.submit input {padding:5px;}

/* error highlighting */
div.input input.form-error, div.input textarea.form-error { background-color:#F7BFBE;}
fieldset legend {font-weight:bold;} 


В этом файле написана стилизация под верстку которую нам выдают новые кейковские хелперы.
Рассмотрим простой

Пример формы для логина


<h2> Login </h2>

<?=$form->create('User', array('action'=>'login'))?>

 <?php echo $form->input('User.email', array(
   
'size' => '30'
   
'label'=>'Email:'
   
'before' => '<div class="tip-message"> '.__('Enter your email or login'true).'</div>',
   
'error' => 'Invalid e-mail/password')) ?>
 
 <?php echo $form->input('User.passwd', array('size' => '30''label' => 'Password:')) ?>

<?=$form->end('Login')?>

С вышеприведенным стилем, наша форма будет выглядеть примерно так:
Пример формы логина

В результате рендеринга, в ответ мы получим такой xhtml:

<h2> Login </h2>

<form id="UserLoginForm" method="post" action="/users/login">

<fieldset style="display:none;">
<input type="hidden" name="_method" value="POST" />
</fieldset>

<div class="input required">
  <div class="tip-message"> Enter your email or login</div>
  <label for="UserEmail">Email:</label>
  <input name="data[User][email]" type="text" size="30" maxlength="200" value="" id="UserEmail" />
</div>

<div class="input required">
  <label for="UserPasswd">Password:</label>
  <input type="password" name="data[User][passwd]" size="30" value="" id="UserPasswd" />
</div>

<div class="submit">
  <input type="submit" value="Login" />
</div>

</form>


Для того чтобы лучше понять что же происходит с результирующим html при вводе невалидных данных, вглянем также на другой вариант html-а, но уже с ошибками при валидации:

<h2> Login </h2>

<form id="UserLoginForm" method="post" action="/users/login">
<fieldset style="display:none;"><input type="hidden" name="_method" value="POST" /></fieldset>

<div class="input required">
 <div class="tip-message"> Enter your email or login</div>
 <label for="UserEmail">Email:</label>
 <input name="data[User][email]" type="text" size="30" maxlength="200" value="" id="UserEmail" class="form-error" />
  <div class="error-message">Invalid e-mail/password</div>
</div>


<div class="input required">
  <label for="UserPasswd">Password:</label>
  <input type="password" name="data[User][passwd]" size="30" value="" id="UserPasswd" />
</div>

<div class="submit">
 <input type="submit" value="Login" />
</div>

</form>


Как можно заметить из вышеприведенного кода, кейковский хелпер добавил нам class="form-error" на поле ввода которое не прошло валидацию, а также вывел дополнительный <div> с указанием ошибки. Все это отображается в таком виде:
Пример формы логина которая не прошла валидацию

Модель

Перейдем к модели, на которую наша форма завязана и сразу заметим, что приведенный выше пример с явным указанием сообщения об ошибки в хелпере является не очень удачной практикой,
по той простой причине, что на одно и то же поле у вас может быть сразу несколько сообщений. В нашем примере для поля username — таких как минимум два:
Также, не будем забывать про интернационализацию(i18n) и локализацию (l10n) — все ваши сообщения об ошибках придется в какой-то момент переводить, поэтому об этом надо позаботиться заранее.


Итак, модель Users, которая отвечает за хранение пользовательских логинов и паролей.
<?php
class User extends AppModel {

    var 
$name 'User';
    var 
$validate = array(

        
//username
        
'username' => array(
            
'custom1' => array(
                
'rule' => array('custom'"/^[a-z0-9][a-z0-9.-_]{2,18}$/i") ,
                
'allowEmpty' => false,
                
'message'    => 'Username can contain only a-z,0-9, "-", 
                                     "_" & "." and cannot be more then 18 characters'
,
            ),
            
'custom2' => array(
                
'rule' => array('checkUnique''username') ,
                
'message' => 'This username is already registered. Try other.',
            )
        ),
        
        
// password
        
'password' => array(
            
'custom1' => array(
                
'rule' => array('custom'"/^[a-z0-9][a-z0-9.-_]{2,50}$/i") ,
                
'allowEmpty' => false,
                
'message'    => 'Password can contain only a-z,0-9, "-", "_" & "." and 
                                      cannot be more then 50 characters and should start with a letter or number'
            
) ,
            
'custom2' => array(
                
'rule' => array('checkSamePassword') ,
                
'message' => 'Passwords must be the same!'
            
),
        ) ,
        
      
// email
        
'email' => array(
            
            
'custom1' => array(
                
'rule' => array('checkUnique''email'),
                
'message' => 'This email is already registered.'
            
),
            
            
'custom2' => array(
                
'rule' => array('custom',  VALID_EMAIL) ,
                
'allowEmpty' => false,
                
'message'    => 'Input valid email'
            
)
        ),
       
         
'first_name' => array('rule' => VALID_NOT_EMPTY,
                                      
'message' => 'Cannot be left empty'),
         
'last_name' => array('rule' => VALID_NOT_EMPTY
                                     
'message' => 'Cannot be left empty')
    );

/* i18n

 _('Cannot be left empty')
 _('Username can contain only a-z,0-9, "-", "_" & "." 
    and cannot be more then 18 characters')
 _('Password can contain only a-z,0-9, "-", "_" & "." and cannot be more 
    then 50 characters and should start with a letter or number')
 _('Passwords must be the same!')
*/

    /**
     * Cake's callback
     *
     * @return true/false
     */
    
function beforeSave() {
        if (
$this->data['User']['password'] != '') {
            
$this->data['User']['password'] = md5($this->data['User']['password']);
        } else {
            unset(
$this->data['User']['password']);
        }
       
        return 
true;
    }

    
/**
     * Check identity of the password and the password_again fields
     *
     * @param array $data
     * @return boolean
     */
    
public function checkSamePassword($data) {
     
        return 
$this->data['User']['password'] == $this->data['User']['password_again'];
    }

    
    
/**
     * check Unique fields
     *
     * @param array $data
     * @param string $fieldName
     * @return unknown
     */
    
public function checkUnique($data$fieldName) {
        
$valid false;
        
        if (isset(
$fieldName) && $this->hasField($fieldName)) {
            
$valid $this->isUnique(array(
                
$fieldName => $this->data['User'][$fieldName]
            ));
        }
 
        return 
$valid;
    }


}
?>


Рассмотрим код модели подробней.

Как можно заметить, сообщения об ошибках на определенные поля больше подходят для формы регистрации, нежели логина. Потенциальному хакеру совсем необязательно выводить информацию о том какие у нас ограничения на пароль, и есть ли у нас уже такой логин в базе.

Поэтому, далее, будем предполагать что у нас форма регистрации, а не логина.
Рассмотрим детальней описание валидации для поля User.username:

<?php
  
'username' => array(
            
'custom1' => array(
                
'rule' => array('custom'"/^[a-z0-9][a-z0-9.-_]{2,18}$/i") ,
                
'allowEmpty' => false,
                
'message'    => 'Username can contain only a-z,0-9, "-", 
                                     "_" & "." and cannot be more then 18 characters'
,
            ),
            
'custom2' => array(
                
'rule' => array('checkUnique''username') ,
                
'message' => 'This username is already registered. Try other.',
            )
        )
  
?>

На это поле у нас задано два правила для валидации custom1 и custom2. Имена для этих правил можно выбирать произвольно, для Кейка — это всего лишь ключи.
На каждое правило задается метод валидации, в первом — это проверка по пользовательскому регулярному выражению, во втором — это вызов callback функции Users::checkUnique(), с вторым параметром равным username.

Правила выполняются в том порядке в котором записаны в модели. То есть, если сработало правило custom1, и за ним custom2 — то в результирующей форме вы увидите сообщение об ошибке “This username is already registered. Try other.”

Заметим также что записи типа

<?php 
'first_name' => array('rule' => VALID_NOT_EMPTY,
                             
'message' => 'Cannot be left empty'),
?>


и 

<?php
'first_name' => array(
 
'custom1' => array(
                
'rule' => array('custom',  VALID_NOT_EMPTY) ,       
                
'message'    => 'Cannot be left empty'
))
?>


будут эквивалентны по результату.

В Cake 1.2 существует целое множество встроенных проверок для стандартных случаев, среди них такие

Для более детального разбирательства с тем что можно описывать в полях типа custom нужно глянуть в раздел по Валидации данных на CakePHP
Теперь рассмотрим валидацию со стороны контроллера

Контроллер


Рассмотрим вариант с формой логина
<?php

class UsersController extends AppController {

    var 
$name          'Users';
    var 
$uses           = array('User');
    var 
$components = array('obAuth')

  
/**
     * Action for user login
     *
     */
    
function login() {
        
        if (isset(
$this->data['User'])) {
            
// login using obAuth component
            
if($this->obAuth->login($this->data['User'])) {
                
$this->redirect('/users/index');
            } else {
                
// invalidate form
                
$this->User->invalidate('email');
            }
        }
    }

    
/**
     * Creates new user
     *
     */
    
function register() {
        if (!empty(
$this->data)) {
            
$data $this->data;    
            if (
$this->User->save($data)) {
                
$this->redirect('/pages/welcome');
            }        
        }
    }


}

?>

В данном примере используется сторонний компонент для авторизации и аутентификации obAuth
Итого, у нас два действия:

Обратим внимание на функцию User->invalidate() которая занимается только тем что помечает поле email как непрошедшее валидацию, а значит включает отображение ошибок именно на это поле.
Для логина, нам достаточно выводить ошибку только на одно поле, в нашем конкрентом примере — email.

Что до действия register — то оно довольно стандартное, после удачной валидации и сохранения происходит редирект на страницу welcome.

Материалы по теме

Cake 1.2 Data validation from the official manual
Validation with CakePHP 1.2
All about validation in CakePHP 1.2
Компонент obAuth



1 2 3 4 5

Последние комментарии:

Обсудить (комментариев: 0)