第9章 Field Validation的历程

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们第4集主要讲解开发与代码,当然很多代码是在前面的网上书店系统里面都用到的,部分代码是对网上书店原有功能的补充。此外,他不是一个严格意义上面的参考书,但是希望这份资料对大家仍然有用。

我们先来看一个简单的,这个对我来说很简单的,因为代码已经写好了,还放到了网络上了,这就是我写的Field validation模块。我们这一章,主要讲解field validation的演化历程,讲解里面代码的变化,用到的Drupal7的相关技术。如果让我凭空写出来一个模块进行讲解,效果未必会怎么好。

其实很久很久以前,我一直都有一个愿望,就是在drupal.org上贡献几个模块,而且这几个模块还比较流行,这一直是我的心愿。从最初的uc_alipay,到block_morelink,两个模块的用户量都是比较小的,uc_alipay我觉得怎么也能发展到56百的安装量,但是实际只有80+多个,block_morelink我觉得可以达到1000,或者更多,但是实际也仅仅只有100+个。每当看到东哥(eastcn, http://drupal.org/user/134014)在drupal.org上面的安装量,达到了几千多个,http://drupal.org/project/photos最多的时候达到过2000+的,自己都艳羡不已。什么时候,自己写的模块的安装量 ,能够超过东哥。

榜样的力量永远会激励着人,只要你没有忘记这个念头。机会总要来的,我在帮助外研社改造他们的社网时,遇到了这样的需求,字段的验证问题。当时章林提出了一个想法,如果Drupal的验证,能够像.net里面的那样方便就可以,最初是用来验证webform的,然后我就告诉他,有webform_validation这样的模块,他用了之后,觉得很好用;再后来,我们遇到了字段的验证,实体表单里面的字段验证,章林又提出了一个想法,如果字段(Field)的验证能够像webform的验证那样简单就可以了,只需要能够配置正则表达式就可以了,这样会方便很多。

这是一个很好的想法,我检查了所有的与验证相关的模块,没有找到一个是用来处理字段验证的,有一个Validation API,只有Drupal6的版本,而且已经无人维护了。还有CCK Validation。那个时候,Drupal7刚刚出来,很多模块都没有跟上。这是一个机会。


Drupal版本:

1 最初的解决办法

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

但是项目中的实际问题,还是需要解决的。在实际的项目中,章林选择是用了form_alter来解决这个问题。Form_alter我们在前面,Think in Drupal第一集里面的第一章讲的就是这个钩子,它是非常有用的,在这里解决这个问题也是非常有效的。首先确定实体表单的ID,然后通过form_alter为这个表单添加一个验证回调函数,在自己的验证函数里面添加自己的逻辑。原有的代码已经找不到了,我在网络上找了段类似的代码,做了一下改造:

function mymodule_form_alter(&$form, &$form_state, $form_id) { 

  if ($form_id == 'test_node_form') { 

    $form['#validate'] = 'mymodule_custom_validation_handler'; 

  } 

}

 

function mymodule_custom_validation_handler((&$form, &$form_state){

  $length = strlen($form_state['values']['field_testlength']['und']['0']['value'])

  if ($length > 8) { 

    form_set_error("field_testlength']['und']['0']['value", t('长度必须小于等于8')); 

  } 

}

这段程序我没有调试,用的时候,自己需要检查一下。这里需要注意的是,获取字段值的方法:

$form_state['values']['field_testlength']['und']['0']['value']

以及设置错误消息时,第一个参数:

"field_testlength']['und']['0']['value"

另一个需要注意的是,字段的语言属性,['und'],这里究竟是用und还是默认语言,用的时候也需要自己检查一下。

 

我觉得,解决Drupal7下面的字段验证,应该有更好一点的钩子,我便寻找,寻找,终于找到了一个可能工作的钩子。我的解决办法是这样的:

 

/**

 * Implements hook_field_attach_validate().

 */

function field_validation_field_attach_validate($entity_type, $entity, &$errors) {

  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);

  if($entity_type =='node' &&  $bundle = 'test'){

    if($field_text = $entity->field_text){

      if(strlen($field_text['und'][0]['value'])>8){

        $errors['field_text']['und'][0][] = array(

          'error' => 'field_text', 

          'message' => t('length must less than 8.'),

        );

      }

    }

  }

}

这是最原始的测试代码,它仅仅用来说明了hook_field_attach_validate是可以用来解决这个问题。这也是我第一次使用这个钩子函数。而以后,有关Field的验证问题,一直都是通过这个钩子解决的。首先,我觉得这种方式比form_alter高级一点,Dries曾经写过一篇介绍Drupal7的文章,里面就讲到了字段的验证,与表单的验证分离开了,这在Drupal7里面是一个进步。而这种分离,在Drupal8里面还会进一步的改进。我在drupal.org上面贡献了field validation模块以后,曾经有人建议使用Form_alter的形式,但是我没有理他们,还是坚持使用hook_field_attach_validate

另外的值得注意的是,这段代码:

list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);

这是我从别处拷贝过来的。这里根据一个实体对象、还有实体类型,使用entity_extract_ids这个API函数,便可以提取出来它的idvidbundle。这是很有用的,但是有时候,这个API函数并不能正常工作,个别时候,大部分都是可以的。

此外,这个错误消息的设置:

        $errors['field_text']['und'][0][] = array(

          'error' => 'field_text', 

          'message' => t('length must less than 8.'),

        );

这个,我们在第一集的,字段API一章里面介绍过了这个结构。我最初编写的时候,并不知道这两个键的含义,尤其是'error',所以默认把它设置为了字段名字。'message'的含义我是清楚的,用来设置具体的错误消息。

代码并不是一下子就工作了,我还做了很多的调试工作。还是使用原始的drupal_set_messagedebug

有了hook_field_attach_validate,我们便不需要考虑具体的表单ID了,因为内容类型很多,实体类型也很多。我们不可能处理所有的情况,而hook_field_attach_validate可以帮助我们从这个问题中解脱出来。我在选用这个钩子以前,应该读过Field Attach APIhttp://api.drupal.org/api/drupal/modules%21field%21field.attach.inc/group/field_attach/7,以及http://api.drupal.org/api/drupal/modules%21field%21field.module/group/field/7, 当然,我还在api.drupal.org上面搜索相关的钩子函数:

图片1.png 

它的触发函数,field_attach_validate,我应该也读过的:

function field_attach_validate($entity_type, $entity) {

  $errors = array();

  // Check generic, field-type-agnostic errors first.

  _field_invoke_default('validate', $entity_type, $entity, $errors);

  // Check field-type specific errors.

  _field_invoke('validate', $entity_type, $entity, $errors);

 

  // Let other modules validate the entity.

  // Avoid module_invoke_all() to let $errors be taken by reference.

  foreach (module_implements('field_attach_validate') as $module) {

    $function = $module . '_field_attach_validate';

    $function($entity_type, $entity, $errors);

  }

 

  if ($errors) {

    throw new FieldValidationException($errors);

  }

}

我读的时候不够细致,忽略里面的注释。总之,我做了很多的准备工作的。


Drupal版本:

10 field_validation_field_delete

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

field_validation_field_deletehook_field_delete的钩子实现,这段代码的作用是,当删除一个字段时,删除字段上面的验证规则。这里面用到的field_dynamic_delete_rule函数,命名不符合Drupal的规范,最好命名为field_validation_dynamic_delete_rulefield_validation_field_deletefield_dynamic_delete_rule的名字也都是来源于webform_validation的。在webform_validation里面,使用的钩子函数是hook_node_delete,当一个节点被删除时,删除节点上面的验证规则,其实我觉得webform_validation里面应该检查一下这个节点的类型是不是webform形式的,不是的话直接跳过,这样效率会更高一点。


Drupal版本:

11 field_validation_rule_save

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

field_validation_rule_save函数是用来保存验证规则的,我现在觉得应该放到field_validation.rules.inc文件中去。这里值的学习的是drupal_write_record的使用,这个函数能够帮助我们少写很多代码,插入还是更新,对于程序员来说,我们经常需要写if语句做判断的,而drupal_write_record将插入和更新,很好的结合了起来:

drupal_write_record('field_validation_rule', $values);

drupal_write_record('field_validation_rule', $values, 'ruleid');

当然,这个函数里面再次调用了module_invoke_all,这意味着,我们的模块又提供了一个钩子函数供其它模块交互。这样写有好的地方,就是其它模块不用修改代码,就可以与我们的系统交互;不好的地方是,field_validation是一个很孤立的小功能,很少会有其它系统与我们交互,我的意思是说,这里的这个钩子是多余的,单纯为了创建钩子函数而已。当然这里的代码也是直接复制过来的。对当时的我来说,这个字段验证的模块已经足够复杂了。所以直接借鉴webform_validation的代码。如果让我自己写,是写不出来这样的代码的。


Drupal版本:

12 field_validation.validators.inc

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

这是field_validation.validators.inc文件的:

<?php

 

/**

 * @file

 * Provides validation functionality and hooks

 */

 

/**

 * Implements hook_webform_validation_validators().

 *

*/

function field_validation_field_validation_validators() {

  return array(

    'regex' => array(

      'name' => "Regular expression",

      'component_types' => array(

        'textfield',

        'textarea',

        'email',

        'hidden',

      ),

      'custom_error' => TRUE,

      'custom_data' => array(

        'label' => t('Regex code'),

        'description' => t('Specify regex code to validate the user input against.'),

      ),

      'description' => t("Validates user-entered text against a specified regular expression. Note: don't include delimiters such as /."),

    ),

  );

}

 

/**

 * Implements hook_webform_validation_validate().

 */

function field_validation_field_validation_validate($validator_name, $items, $rule) {

  if ($items) {

    $error = array();

    switch ($validator_name) {

     

      case "regex":

        mb_regex_encoding('UTF-8');

        $regex = $rule['data'];

        foreach ($items as $delta => $item) {

          if ($item['value'] != '' && (!mb_ereg("$regex", $item['value']))) {

$error[$delta][] = array(

              'error' => 'regex', 

              'message' =>$rule['error_message'],

            );

//debug($errors);

          }

        }

        return $error;

        break;

 

    }

  }

}

 

function _field_validation_flatten_array($val) {

 …

}

 

/**

 * Get a list of validator definitions

 */

function field_validation_get_validators() {

  $validators = module_invoke_all("field_validation_validators");

  // let modules use hook_webform_validator_alter($validators) to change validator settings

  drupal_alter('field_validator', $validators);

  return $validators;

}

 

…….

function field_validation_get_validators_selection() {

….

}

……

function field_validation_valid_component_types($validator) {

}

….

function _field_validation_all_allowed($allowed) {

….

}

function field_validation_get_validator_info($validator_key) {

}

….

function _field_validation_i18n_error_message($rule) {

….

}

……

function _field_validation_check_false($var) {

……

}

….

function _field_numeric_check_data($data) {

….

}

这个文件里面,我知道的好像只有field_validation_field_validation_validatorsfield_validation_field_validation_validatefield_validation_get_validators是有用的。其它的大部分都是特定于webform_validation的,当时是一股脑的复制了过来的,然后一个一个的字符串替换。我们看到这里保留了大量的webform_validation的信息。


Drupal版本:

13 field_validation_field_validation_validators

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

field_validation_field_validation_validators是一个钩子函数,实现的就是我们自己定义的钩子hook_field_validation_validators,它类似于hook_menu,返回的是一个数组,在这个数组里面,可以定义多个验证器,我们这里只定义了一个regex,正则表达式。在单个验证器数组里面,它包含5个键,namecomponent_typescustom_errorcustom_datadescription。都是直接来自于webform_validation的。我在这里出于方便的考虑,做了取舍,比如每个验证器都是需要自定义错误消息的,而在webform_validation里面,只有部分验证器可以自定义错误消息,为什么?其中一个原因就是,这些错误消息,需要用到英文,如果将来定义的验证器类型比较多的话,需要写很多这样的英文,自己的英文读还可以,写就不行了,怕出问题;另外一个就是,如果用英文的,还需要翻译,这对很多非英文的用户不方便,特别是中文用户;出于这两点考虑,我全部采用了自定义消息。这里的component_types其实应该改为field_types,表示这个验证器适用于哪些字段类型,实际上,我在程序里面,省去了这一检查,就是说一个验证器适用于所有的字段类型,这也是一个改进。在webform_validation,只有适用于一个组件类型的验证器,才能应用到该组件上,webform的组件类型比较少,主要定义在webform模块里面,而field的类型,除了Drupal核心自带的,很多第三方模块还定义了很多。我希望一个验证器可以应用于所有的字段类型,这一点上面不加限制,至于是否真的适用,由用户自己决定,而不是交给程序,写死在这里。


Drupal版本:

14 field_validation_field_validation_validate

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

field_validation_field_validation_validate这也是一个钩子函数的实现,这里面包含了验证器的验证规则。当传递过来的数据没有通过正则验证时,我们返回一个包含错误信息的数组。这个错误消息,应该通过引用传递的,但是module_invoke_all不支持引用传递。所以只好这样变通一下


Drupal版本:

15 field_validation_get_validators

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

函数field_validation_get_validators用来获取所有的字段验证器,这里再次使用了module_invoke_allhook_field_validation_validators就是在这里定义的,这里还使用了:

drupal_alter('field_validator', $validators);

这样其它模块就可以通过钩子hook_field_validator_alter来修改验证器定义了。

 

field_validation_get_validators_selectionfield_validation_get_validator_info这两个函数还是有一点用的,不过只在field_validation.admin.inc中用到。


Drupal版本:

16 field_validation.rules.inc

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

这是field_validation.rules.inc文件的:

<?php

 

/**

 * @file

 * provides API and management functions for the field validation rules

 */

 

/**

 * Get a rule entry

 */

function field_validation_get_rule($ruleid) {

  $result = db_query("SELECT * FROM {field_validation_rule} WHERE ruleid = :ruleid", array(':ruleid' => $ruleid), array('fetch' => PDO::FETCH_ASSOC));

  $rule = $result->fetchAssoc();

  return $rule;

}

 

/**

 * Get an array of rules assigned to a field instance

 */

function field_validation_get_field_rules($instance) {

  $rules = array();

$bundle = $instance['bundle'];

  $entity_type = $instance['entity_type'];

  $field_name = $instance['field_name'];

$sql = "SELECT * FROM {field_validation_rule} WHERE field_name = :field_name AND entity_type = :entity_type AND bundle = :bundle ORDER BY ruleid DESC";

  $result = db_query($sql, array(':field_name' => $field_name, ':entity_type' => $entity_type, ':bundle' => $bundle), array('fetch' => PDO::FETCH_ASSOC));

  foreach ($result as $rule) {

    $rules[$rule['ruleid']] = $rule;

  }

  return $rules;

}

 

/**

 * Get an array of rules assigned to a field instance

 */

function field_validation_get_bundle_rules($entity_type, $bundle) {

  $rules = array();

$sql = "SELECT * FROM {field_validation_rule} WHERE entity_type = :entity_type AND bundle = :bundle ORDER BY ruleid DESC";

  $result = db_query($sql, array(':entity_type' => $entity_type, ':bundle' => $bundle), array('fetch' => PDO::FETCH_ASSOC));

  foreach ($result as $rule) {

    $rules[$rule['ruleid']] = $rule;

  }

  return $rules;

}

 

 

/**

……

 */

function field_validation_rule_components_basic($components) {

……

}

 

/**

 * Delete a rule and dependencies

 */

function field_dynamic_delete_rule($ruleid) {

  // delete rule

  db_delete('field_validation_rule')

  ->condition('ruleid', $ruleid)

  ->execute();

}

这个文件里面主要包含了有关验证规则API函数,比如删除规则field_dynamic_delete_rule,获取一个规则field_validation_get_rule,获取一个字段上的规则field_validation_get_field_rules,获取一个bundle上面的规则field_validation_get_bundle_rules

这里的field_validation_rule_components_basic是复制过来的,这里应该没有什么用处。

    这里的代码没有什么特别的,主要就是数据库的查询操作,看看这里的代码或许对于熟悉Drupal7下面的数据库操作有点帮助,不过这里的查询方式都是最原始的db_query。在后面的项目中,我更倾向于使用EntityFieldQuery


Drupal版本:

17 field_validation.admin.inc

 作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

这是field_validation.admin.inc文件的:

<?php

 

/**

 * @file

 * Manages validation rules administration UI

 */

 

/**

 * Menu callback function to show an overview of the existing validation rules, and the option to add a rule

 */

function field_validation_manage($instance) {

  $rules = field_validation_get_field_rules($instance);

  $output = '';

  $output .= theme('field_validation_manage_overview', array('rules' => $rules, 'instance' => $instance));

  $output .= theme('field_validation_manage_add_rule', array('instance' => $instance));

$output .= '123456';

  return $output;

}

 

/**

 * Themable function to list the rules assigned to a webform

 */

function theme_field_validation_manage_overview($variables) {

  $rules = $variables['rules'];

  $instance = $variables['instance'];

 

  $header = array(t('Rule name'), t('Validator'), array(

      'data' => t('Operations'),

      'colspan' => 2,

    ));

  $validators = field_validation_get_validators_selection();

  if (!empty($rules)) {

    foreach ($rules as $rule) {

      $row = array();

      $row[] = array(

        'data' => $rule['rulename'],

      );

      $row[] = array(

        'data' => $validators[$rule['validator']],

      );

  $path = isset($_GET['q']) ? $_GET['q'] : '';

      $row[] = array(

        'data' => l(t('Edit'), $path.'/edit/' . $rule['validator'] . '/' . $rule['ruleid'], array("query" => drupal_get_destination())),

      );

      $row[] = array(

        'data' => l(t('Delete'), $path.'/delete/' . $rule['ruleid'], array("query" => drupal_get_destination())),

      );

      $rows[] = $row;

    }

  }

  else {

    $rows[][] = array(

      'data' => t('No validation rules available.'),

      'colspan' => 5,

    );

  }

 

  return theme('table', array('header' => $header, 'rows' => $rows));

}

 

/**

 * Callback function to add or edit a validation rule

 */

function field_validation_manage_rule($form, $form_state, $instance, $action = 'add', $validator = 'regex', $ruleid = NULL) {

  $form = array();

  $rule_validator = field_validation_get_validator_info($validator);

  $rule = field_validation_get_rule($ruleid);

  $form['rule'] = array(

    '#type' => 'fieldset',

    '#title' => ($action == 'edit') ? t('Edit rule') : t('Add rule'),

    '#collapsible' => FALSE,

    '#collapsed' => FALSE,

  );

 

  $form['rule']['validator'] = array(

    '#type' => 'hidden',

    '#value' => $validator,

  );

 

  $form['rule']['action'] = array(

    '#type' => 'hidden',

    '#value' => $action,

  );

 

  if ($action == 'edit' && $rule) {

    $form['rule']['ruleid'] = array(

      '#type' => 'hidden',

      '#value' => $rule['ruleid'],

    );

 

    $form['rule']['field_name'] = array(

      '#type' => 'hidden',

      '#value' => $rule['field_name'],

    );


  $form['rule']['entity_type'] = array(

      '#type' => 'hidden',

      '#value' => $rule['entity_type'],

    );


  $form['rule']['bundle'] = array(

      '#type' => 'hidden',

      '#value' => $rule['bundle'],

    );

  }

  else {

    $form['rule']['field_name'] = array(

      '#type' => 'hidden',

      '#value' => $instance['field_name'],

    );


  $form['rule']['entity_type'] = array(

      '#type' => 'hidden',

      '#value' => $instance['entity_type'],

    );


  $form['rule']['bundle'] = array(

      '#type' => 'hidden',

      '#value' => $instance['bundle'],

    );

  }

 

  $form['rule']['rulename'] = array(

    '#type' => 'textfield',

    '#title' => t('Rule name'),

    '#default_value' => (isset($rule['rulename'])) ? $rule['rulename'] : '',

    '#required' => TRUE,

    '#size' => 60,

    '#maxlength' => 255,

    '#weight' => 1,

  );

 

  if (isset($rule_validator['custom_data']) && is_array($rule_validator['custom_data'])) {

    $required = isset($rule_validator['custom_data']['required']) ? $rule_validator['custom_data']['required'] : TRUE;

    $form['rule']['data'] = array(

      '#type' => 'textfield',

      '#title' => $rule_validator['custom_data']['label'],

      '#description' => $rule_validator['custom_data']['description'],

      '#required' => ($required !== FALSE) ? TRUE : FALSE,

      '#size' => 60,

      '#maxlength' => 255,

      '#default_value' => $rule['data'],

      '#weight' => 4,

    );

  }

 

  if (isset($rule_validator['custom_error'])) {

    $form['rule']['error_message'] = array(

      '#type' => 'textfield',

      '#title' => t('Custom error message'),

      '#description' => t("Specify an error message that should be displayed when user input doesn't pass validation"),

      '#required' => TRUE,

      '#size' => 60,

      '#maxlength' => 255,

      '#default_value' => $rule['error_message'],

      '#weight' => 5,

    );

  }

 

  $form['rule']['submit'] = array(

    '#type' => 'submit',

    '#value' => (isset($rule['ruleid'])) ? t('Edit rule') : t('Add rule'),

    '#weight' => 25,

  );

 

  return $form;

}

 

/**

 * Validation handler to add / edit a rule

 */

function field_validation_manage_rule_validate($form, &$form_state) {

  $values = $form_state['values'];

  if ($values['action'] == 'edit') {

    if (!is_numeric($values['ruleid']) || $values['ruleid'] == 0) {

      form_set_error(NULL, t('A problem occurred while editing this rule. Please try again.'));

    }

  }

}

 

 

/**

 * Submit handler to add / edit a rule

 */

function field_validation_manage_rule_submit($form, &$form_state) {

  $values = $form_state['values'];

  field_validation_rule_save($values);

}

 

/**

 * Confirmation form to delete a rule

 */

function field_validation_delete_rule($form, &$form_state, $rule) {

  if (isset($rule['ruleid'])) {

    $form['ruleid'] = array(

      '#type' => 'value',

      '#value' => $rule['ruleid'],

    );

  }

 

  return confirm_form($form,

    t('Are you sure you want to delete the rule %name?', array('%name' => $rule['rulename'])),

    isset($_GET['destination']) ? $_GET['destination'] : $_GET['q'],

    t('This action cannot be undone.'),

    t('Delete'),

    t('Cancel')

  );

}

 

/**

 * Submit handler to delete a rule

 */

function field_validation_delete_rule_submit($form, &$form_state) {

  $ruleid = $form_state['values']['ruleid'];

  field_dynamic_delete_rule($ruleid);

  module_invoke_all('field_validation', 'rule', 'delete', $ruleid);

}


Drupal版本:

18 确认表单的使用

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

admin.inc文件,负责的是Field validationUI界面,用来向字段添加规则、编辑规则、删除规则、浏览规则的。这里面有几点可供借鉴,一个是确认表单的使用:

return confirm_form($form,

    t('Are you sure you want to delete the rule %name?', array('%name' => $rule['rulename'])),

    isset($_GET['destination']) ? $_GET['destination'] : $_GET['q'],

    t('This action cannot be undone.'),

    t('Delete'),

    t('Cancel')

  );

通常在删除一个东西的时候,使用这个确认表单,当用户执行一个动作,而这个动作又是不可以撤销的情况下,我们可以让用户确认一下,这个时候如果用户是不小心点到的,看到警告信息,可能就会取消操作。


Drupal版本:

19 添加/编辑表单合二为一

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

另外就是添加/编辑验证规则的表单,使用了同一个函数field_validation_manage_rule,只不过在这个函数内部,做了判断。这样的话,便可以达到复用代码的作用,因为添加和编辑表单两者之间,相似度是非常大的,在Drupal内部,将两者合并是最常见的用法。我以前写程序的时候,最开始开发Drupal的时候,总是将两者分开写。

另外就是theme(‘table’)的使用,输出一个表格也没有什么难的,这里提供了一个具体的例子。


Drupal版本:

2 Field validation的Alpha1版

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我在hook_field_attach_validate的实验成功以后,便开始寻求更一般的解决办法了。当时章林已经使用form_alter解决这个问题了,我同时也告诉了他在Drupal7下面,可以采用hook_field_attach_validate来解决这样的问题。如果仅仅是为了满足项目的需要,这个问题就可以这样结束了。每当我们遇到字段验证的时候,套用已有的代码,改造改造就可以了。

但是,我并没有这样停止下来。寻找更简单的解决办法,让字段验证可以像 webform验证一样,可以通过配置来解决问题,这对于很多人来说,是很有用的。借用模块webform_validation的名字,改造一下field_validation,我发现这个名字竟然没有被占用。想一想吧,很多好的模块名字,早已经被占用完毕了。我多少觉得自己非常的幸运。

我已经想象出来了,这个模块未来将会被上万多个站点使用,它的应用范围,与webform_validation相比,用途更广,用户量更大。我是希望它能够成为TOP 100里面的一员。这是我很早很早以前,便有的一个想法,就像希望自己的模块的安装量,能够超过东哥一样。

我在外研社,花了一个下午来开发这个模块,后来回到家里,又花了一个晚上,才有了基本的雏形,紧接着,自己又花了一个周末的时间,终于搞定了最初的alpha1版。在alpha1版里面,实现了通过配置,为字段添加正则表达式验证的功能。

现在,我们可以下载http://drupal.org/node/1148738 里面的alpha1版,来看看里面的代码。此外,大家也可以到http://drupal.org/node/1107346, 下载webform validation7.x-1.0版。我最初的代码,很多都是从webform validation里面复制过来的。

首先,我从头到尾的完整的读过webform validation的代码,觉得很不错,至少那个时候,觉得这个已经相当复杂了。同时,我还测试过webform validation模块的具体用法。但是将webform validation改造成field_validation的过程也是相当的痛苦的,调试,调试再调试。大致的过程是这样的,首先是数据库设计,也就是install文件,这个可以工作;接着是自己手工的在数据库里面,插入一个验证规则,然后检查这个验证规则,能够正常工作;最后是编写规则的配置界面。第一步比较简单,第二步是关键,第三步就是规则的增删改查,一个体力活而已,但是调试起来比较麻烦。


Drupal版本:

20 补充说明

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

代码里面问题很多,我把这样的代码放到了drupal.org上面去以后,有人用过以后,给出了大量的改进意见,还有人提出了赤裸裸的批评。不过alpha1里面的代码,是可以工作的,很好的解决了我们实际中的问题。

其实阅读别人的代码是一个很痛苦的事情,上来我就放了这么多的代码,我想很多人都不愿读下去了。学Drupal也一样,只有足够的代码阅读量以后,写起模块才会得心应手。我读过Drupal5的源代码、Drupal6的源代码,Ubercart的源代码,Commerce的源代码,Views的源代码,Drupal7的大部分源代码。所以对于学Drupal的人,想以这个技能为生的人,建议找一些优秀的模块,读读别人写的代码。读一读自己的同事写的代码。

读完field validation的代码以后,再读读webform validation的代码,别骂我无耻啊,很多代码,基本上都是原封不动的复制过来的。我仅仅把webfrom替换成为了field而已。不过我还是有点自知之明的,在field validation的项目页面,介绍了这些代码的出处,直接说明了,是从webform validation模块里面复制过来的。也算是对webform validation模块作者的一种尊敬。我觉得这一点,至少比国内的很多人,我讲的那些抄袭过后连名字都改了的人,好很多。

Alpha1版,带着众多的问题,问世了,那个时候,drupal.org正在从CSV转到GIT,我对GIT的操作不熟悉,创建了项目页面以后,直接在issue里面传上了代码。后来在小白的帮助下,才学会使用GIT


Drupal版本:

21 Field Validation Beta1版的改进

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

很多人提出了很多的问题,其中一个很简单,就是代码里面带有调试信息,相关问题可以参看http://drupal.org/node/1157324,问题的提出者是Lullabotericduran。我按照他提交的补丁,去除了调试信息,当然,还删除了很多从webform_validation复制过来的无用的函数。


Drupal版本:

22 删除无用的函数

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

哪些函数无用,没有被其它程序调用的孤立函数是无用的。自己使用nodepadd++的查找功能,逐个函数查找,然后确定,哪些函数确实用不到,用不到的就删除了。

field_validation.validators.inc里面删除了函数:

_field_validation_flatten_array

field_validation_valid_component_types

_field_validation_all_allowed

_field_validation_i18n_error_message

_field_validation_check_false

_field_numeric_check_data

 

field_validation.rules.inc文件里面删除了:

field_validation_rule_components_basic


Drupal版本:

23 使用module_implements替代module_invoke_all

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

axel.rutz还发现了一个bughttp://drupal.org/node/1149684),就是如果向多个字段添加验证规则的话,只有最后一个起作用,我最终是这样解决这个问题的,将field_validation_field_attach_validate里面的代码:

 

$errors[$rule['field_name']][$langcode] = module_invoke_all("field_validation_validate", $rule['validator'], $items, $rule);

替换为:

//module_invoke_all does not work here, so i call it directly.

foreach (module_implements("field_validation_validate") as $module) {

  $function = $module . '_' . "field_validation_validate";

  if (function_exists($function)) {

    $function($rule['validator'], $rule, $langcode, $items,  $errors);

  }

}

 

module_invoke_allmodule_implements的转换,是一个进步,使我认识到module_invoke_all是无法传递引用的。field_validation_field_validation_validate也做了相应的调整,这是调整后的样子:

/**

 * Implements hook_field_validation_validate().

 */

function field_validation_field_validation_validate($validator_name, $rule, $langcode, $items, &$errors) {

  if (!empty($items)) {

    switch ($validator_name) {

      case "regex":

        mb_regex_encoding('UTF-8');

        $regex = $rule['data'];

        foreach ($items as $delta => $item) {

          if ($item['value'] != '' && (!mb_ereg("$regex", $item['value']))) {

    $errors[$rule['field_name']][$langcode][$delta][] = array(

              'error' => 'regex_'.$rule['ruleid'], 

              'message' => t($rule['error_message']),

            );

          }

        }

        break;

    }

  }

}

这里可以传递引用参数&$errors了,这里还有另外一个改进,就是为错误消息使用了t函数。


Drupal版本:

24 补充说明

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

很多人提出了新的功能:

1),验证规则可以导入导出。

2),一个验证规则,可以直接应用到多个字段实例,而不是一个一个的复制。

3),多内置一些正则表达式规则,这样用户就不用输入了。

4),验证规则可以取反。

5),希望能够对字段的数据进行预处理,比如使用trim函数。

6),有人还给出了正则表达式的正确格式,我这里使用的是mb_ereg,很多人开始不习惯。mb_ereg是从webform_validation里面复制过来的,直到前不久(Beta1版发布后的1年后),我才知道PHP里面支持两种格式的正则表达式。

这些新功能在beta1里面并没有实现。在修正了明显的bug以后,我发布了Beta1版。Beta1版里面的改进,就是上面所说的。不久,Lullabotericduran写了一篇文章,专门介绍这个模块,http://www.lullabot.com/articles/module-monday-field-validationLullabot是全球知名的Drupal培训公司,自己编写的模块,能够在他们的首页出现,也是一个莫大的荣誉。随之而来的,是模块被更多的人关注,包括webform_validation的作者svendecabootersvendecabooter有一个想法,就是把表单验证、webform验证、字段验证统一起来。这个想法很好,很早的时候,就有人做了尝试,比如Validation API

Svendecabooter的建议,让我感到诚惶诚恐,他写的webform_validation模块,用户量要远远大于刚出来的Field validation。如果他要写一个涵盖所有验证方面的模块的话,那么Field validation就会被包含到里面,最终也将会被废弃掉,因为大家都采用新的模块了。因此我建议Svendecabooter接管Validation API模块,开发一个Drupal7的版本,来涵盖webform

、字段、表单等验证。Svendecabooter接管了Validation API,但是由于他工作比较忙,一直迟迟没有动工。

Field validation是我的练习自己Drupal技能的模块,那个想凭借着它超越东哥的念头是一致存在的,所以内心深处,是比较不喜欢与Svendecabooter的合作的。


Drupal版本:

25 从Beta2到Beta6

 作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

function field_validation_get_bundle_rules($entity_type, $bundle) {

  $rules = array();

$sql = "SELECT * FROM {field_validation_rule} WHERE entity_type = :entity_type AND bundle = :bundle ORDER BY ruleid DESC";

  $result = db_query($sql, array(':entity_type' => $entity_type, ':bundle' => $bundle), array('fetch' => PDO::FETCH_ASSOC));

  foreach ($result as $rule) {

    $rules[$rule['ruleid']] = $rule;

  }

  return $rules;

}

 

 

/**

……

 */

function field_validation_rule_components_basic($components) {

……

}

 

/**

 * Delete a rule and dependencies

 */

function field_dynamic_delete_rule($ruleid) {

  // delete rule

  db_delete('field_validation_rule')

  ->condition('ruleid', $ruleid)

  ->execute();

}

这个文件里面主要包含了有关验证规则API函数,比如删除规则field_dynamic_delete_rule,获取一个规则field_validation_get_rule,获取一个字段上的规则field_validation_get_field_rules,获取一个bundle上面的规则field_validation_get_bundle_rules

这里的field_validation_rule_components_basic是复制过来的,这里应该没有什么用处。

    这里的代码没有什么特别的,主要就是数据库的查询操作,看看这里的代码或许对于熟悉Drupal7下面的数据库操作有点帮助,不过这里的查询方式都是最原始的db_query。在后面的项目中,我更倾向于使用EntityFieldQuery


Drupal版本:

26 field_validation.admin.inc

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

这是field_validation.admin.inc文件的:

<?php

 

/**

 * @file

 * Manages validation rules administration UI

 */

 

/**

 * Menu callback function to show an overview of the existing validation rules, and the option to add a rule

 */

function field_validation_manage($instance) {

  $rules = field_validation_get_field_rules($instance);

  $output = '';

  $output .= theme('field_validation_manage_overview', array('rules' => $rules, 'instance' => $instance));

  $output .= theme('field_validation_manage_add_rule', array('instance' => $instance));

$output .= '123456';

  return $output;

}

 

/**

 * Themable function to list the rules assigned to a webform

 */

function theme_field_validation_manage_overview($variables) {

  $rules = $variables['rules'];

  $instance = $variables['instance'];

 

  $header = array(t('Rule name'), t('Validator'), array(

      'data' => t('Operations'),

      'colspan' => 2,

    ));

  $validators = field_validation_get_validators_selection();

  if (!empty($rules)) {

    foreach ($rules as $rule) {

      $row = array();

      $row[] = array(

        'data' => $rule['rulename'],

      );

      $row[] = array(

        'data' => $validators[$rule['validator']],

      );

  $path = isset($_GET['q']) ? $_GET['q'] : '';

      $row[] = array(

        'data' => l(t('Edit'), $path.'/edit/' . $rule['validator'] . '/' . $rule['ruleid'], array("query" => drupal_get_destination())),

      );

      $row[] = array(

        'data' => l(t('Delete'), $path.'/delete/' . $rule['ruleid'], array("query" => drupal_get_destination())),

      );

      $rows[] = $row;

    }

  }

  else {

    $rows[][] = array(

      'data' => t('No validation rules available.'),

      'colspan' => 5,

    );

  }

 

  return theme('table', array('header' => $header, 'rows' => $rows));

}

 

/**

 * Callback function to add or edit a validation rule

 */

function field_validation_manage_rule($form, $form_state, $instance, $action = 'add', $validator = 'regex', $ruleid = NULL) {

  $form = array();

  $rule_validator = field_validation_get_validator_info($validator);

  $rule = field_validation_get_rule($ruleid);

  $form['rule'] = array(

    '#type' => 'fieldset',

    '#title' => ($action == 'edit') ? t('Edit rule') : t('Add rule'),

    '#collapsible' => FALSE,

    '#collapsed' => FALSE,

  );

 

  $form['rule']['validator'] = array(

    '#type' => 'hidden',

    '#value' => $validator,

  );

 

  $form['rule']['action'] = array(

    '#type' => 'hidden',

    '#value' => $action,

  );

 

  if ($action == 'edit' && $rule) {

    $form['rule']['ruleid'] = array(

      '#type' => 'hidden',

      '#value' => $rule['ruleid'],

    );

 

    $form['rule']['field_name'] = array(

      '#type' => 'hidden',

      '#value' => $rule['field_name'],

    );


  $form['rule']['entity_type'] = array(

      '#type' => 'hidden',

      '#value' => $rule['entity_type'],

    );


  $form['rule']['bundle'] = array(

      '#type' => 'hidden',

      '#value' => $rule['bundle'],

    );

  }

  else {

    $form['rule']['field_name'] = array(

      '#type' => 'hidden',

      '#value' => $instance['field_name'],

    );


  $form['rule']['entity_type'] = array(

      '#type' => 'hidden',

      '#value' => $instance['entity_type'],

    );


  $form['rule']['bundle'] = array(

      '#type' => 'hidden',

      '#value' => $instance['bundle'],

    );

  }

 

  $form['rule']['rulename'] = array(

    '#type' => 'textfield',

    '#title' => t('Rule name'),

    '#default_value' => (isset($rule['rulename'])) ? $rule['rulename'] : '',

    '#required' => TRUE,

    '#size' => 60,

    '#maxlength' => 255,

    '#weight' => 1,

  );

 

  if (isset($rule_validator['custom_data']) && is_array($rule_validator['custom_data'])) {

    $required = isset($rule_validator['custom_data']['required']) ? $rule_validator['custom_data']['required'] : TRUE;

    $form['rule']['data'] = array(

      '#type' => 'textfield',

      '#title' => $rule_validator['custom_data']['label'],

      '#description' => $rule_validator['custom_data']['description'],

      '#required' => ($required !== FALSE) ? TRUE : FALSE,

      '#size' => 60,

      '#maxlength' => 255,

      '#default_value' => $rule['data'],

      '#weight' => 4,

    );

  }

 

  if (isset($rule_validator['custom_error'])) {

    $form['rule']['error_message'] = array(

      '#type' => 'textfield',

      '#title' => t('Custom error message'),

      '#description' => t("Specify an error message that should be displayed when user input doesn't pass validation"),

      '#required' => TRUE,

      '#size' => 60,

      '#maxlength' => 255,

      '#default_value' => $rule['error_message'],

      '#weight' => 5,

    );

  }

 

  $form['rule']['submit'] = array(

    '#type' => 'submit',

    '#value' => (isset($rule['ruleid'])) ? t('Edit rule') : t('Add rule'),

    '#weight' => 25,

  );

 

  return $form;

}

 

/**

 * Validation handler to add / edit a rule

 */

function field_validation_manage_rule_validate($form, &$form_state) {

  $values = $form_state['values'];

  if ($values['action'] == 'edit') {

    if (!is_numeric($values['ruleid']) || $values['ruleid'] == 0) {

      form_set_error(NULL, t('A problem occurred while editing this rule. Please try again.'));

    }

  }

}

 

 

/**

 * Submit handler to add / edit a rule

 */

function field_validation_manage_rule_submit($form, &$form_state) {

  $values = $form_state['values'];

  field_validation_rule_save($values);

}

 

/**

 * Confirmation form to delete a rule

 */

function field_validation_delete_rule($form, &$form_state, $rule) {

  if (isset($rule['ruleid'])) {

    $form['ruleid'] = array(

      '#type' => 'value',

      '#value' => $rule['ruleid'],

    );

  }

 

  return confirm_form($form,

    t('Are you sure you want to delete the rule %name?', array('%name' => $rule['rulename'])),

    isset($_GET['destination']) ? $_GET['destination'] : $_GET['q'],

    t('This action cannot be undone.'),

    t('Delete'),

    t('Cancel')

  );

}

 

/**

 * Submit handler to delete a rule

 */

function field_validation_delete_rule_submit($form, &$form_state) {

  $ruleid = $form_state['values']['ruleid'];

  field_dynamic_delete_rule($ruleid);

  module_invoke_all('field_validation', 'rule', 'delete', $ruleid);

}



这是field_validation.admin.inc文件的:

<?php

 

/**

 * @file

 * Manages validation rules administration UI

 */

 

/**

 * Menu callback function to show an overview of the existing validation rules, and the option to add a rule

 */

function field_validation_manage($instance) {

  $rules = field_validation_get_field_rules($instance);

  $output = '';

  $output .= theme('field_validation_manage_overview', array('rules' => $rules, 'instance' => $instance));

  $output .= theme('field_validation_manage_add_rule', array('instance' => $instance));

$output .= '123456';

  return $output;

}

 

/**

 * Themable function to list the rules assigned to a webform

 */

function theme_field_validation_manage_overview($variables) {

  $rules = $variables['rules'];

  $instance = $variables['instance'];

 

  $header = array(t('Rule name'), t('Validator'), array(

      'data' => t('Operations'),

      'colspan' => 2,

    ));

  $validators = field_validation_get_validators_selection();

  if (!empty($rules)) {

    foreach ($rules as $rule) {

      $row = array();

      $row[] = array(

        'data' => $rule['rulename'],

      );

      $row[] = array(

        'data' => $validators[$rule['validator']],

      );

  $path = isset($_GET['q']) ? $_GET['q'] : '';

      $row[] = array(

        'data' => l(t('Edit'), $path.'/edit/' . $rule['validator'] . '/' . $rule['ruleid'], array("query" => drupal_get_destination())),

      );

      $row[] = array(

        'data' => l(t('Delete'), $path.'/delete/' . $rule['ruleid'], array("query" => drupal_get_destination())),

      );

      $rows[] = $row;

    }

  }

  else {

    $rows[][] = array(

      'data' => t('No validation rules available.'),

      'colspan' => 5,

    );

  }

 

  return theme('table', array('header' => $header, 'rows' => $rows));

}

 

/**

 * Callback function to add or edit a validation rule

 */

function field_validation_manage_rule($form, $form_state, $instance, $action = 'add', $validator = 'regex', $ruleid = NULL) {

  $form = array();

  $rule_validator = field_validation_get_validator_info($validator);

  $rule = field_validation_get_rule($ruleid);

  $form['rule'] = array(

    '#type' => 'fieldset',

    '#title' => ($action == 'edit') ? t('Edit rule') : t('Add rule'),

    '#collapsible' => FALSE,

    '#collapsed' => FALSE,

  );

 

  $form['rule']['validator'] = array(

    '#type' => 'hidden',

    '#value' => $validator,

  );

 

  $form['rule']['action'] = array(

    '#type' => 'hidden',

    '#value' => $action,

  );

 

  if ($action == 'edit' && $rule) {

    $form['rule']['ruleid'] = array(

      '#type' => 'hidden',

      '#value' => $rule['ruleid'],

    );

 

    $form['rule']['field_name'] = array(

      '#type' => 'hidden',

      '#value' => $rule['field_name'],

    );


  $form['rule']['entity_type'] = array(

      '#type' => 'hidden',

      '#value' => $rule['entity_type'],

    );


  $form['rule']['bundle'] = array(

      '#type' => 'hidden',

      '#value' => $rule['bundle'],

    );

  }

  else {

    $form['rule']['field_name'] = array(

      '#type' => 'hidden',

      '#value' => $instance['field_name'],

    );


  $form['rule']['entity_type'] = array(

      '#type' => 'hidden',

      '#value' => $instance['entity_type'],

    );


  $form['rule']['bundle'] = array(

      '#type' => 'hidden',

      '#value' => $instance['bundle'],

    );

  }

 

  $form['rule']['rulename'] = array(

    '#type' => 'textfield',

    '#title' => t('Rule name'),

    '#default_value' => (isset($rule['rulename'])) ? $rule['rulename'] : '',

    '#required' => TRUE,

    '#size' => 60,

    '#maxlength' => 255,

    '#weight' => 1,

  );

 

  if (isset($rule_validator['custom_data']) && is_array($rule_validator['custom_data'])) {

    $required = isset($rule_validator['custom_data']['required']) ? $rule_validator['custom_data']['required'] : TRUE;

    $form['rule']['data'] = array(

      '#type' => 'textfield',

      '#title' => $rule_validator['custom_data']['label'],

      '#description' => $rule_validator['custom_data']['description'],

      '#required' => ($required !== FALSE) ? TRUE : FALSE,

      '#size' => 60,

      '#maxlength' => 255,

      '#default_value' => $rule['data'],

      '#weight' => 4,

    );

  }

 

  if (isset($rule_validator['custom_error'])) {

    $form['rule']['error_message'] = array(

      '#type' => 'textfield',

      '#title' => t('Custom error message'),

      '#description' => t("Specify an error message that should be displayed when user input doesn't pass validation"),

      '#required' => TRUE,

      '#size' => 60,

      '#maxlength' => 255,

      '#default_value' => $rule['error_message'],

      '#weight' => 5,

    );

  }

 

  $form['rule']['submit'] = array(

    '#type' => 'submit',

    '#value' => (isset($rule['ruleid'])) ? t('Edit rule') : t('Add rule'),

    '#weight' => 25,

  );

 

  return $form;

}

 

/**

 * Validation handler to add / edit a rule

 */

function field_validation_manage_rule_validate($form, &$form_state) {

  $values = $form_state['values'];

  if ($values['action'] == 'edit') {

    if (!is_numeric($values['ruleid']) || $values['ruleid'] == 0) {

      form_set_error(NULL, t('A problem occurred while editing this rule. Please try again.'));

    }

  }

}

 

 

/**

 * Submit handler to add / edit a rule

 */

function field_validation_manage_rule_submit($form, &$form_state) {

  $values = $form_state['values'];

  field_validation_rule_save($values);

}

 

/**

 * Confirmation form to delete a rule

 */

function field_validation_delete_rule($form, &$form_state, $rule) {

  if (isset($rule['ruleid'])) {

    $form['ruleid'] = array(

      '#type' => 'value',

      '#value' => $rule['ruleid'],

    );

  }

 

  return confirm_form($form,

    t('Are you sure you want to delete the rule %name?', array('%name' => $rule['rulename'])),

    isset($_GET['destination']) ? $_GET['destination'] : $_GET['q'],

    t('This action cannot be undone.'),

    t('Delete'),

    t('Cancel')

  );

}

 

/**

 * Submit handler to delete a rule

 */

function field_validation_delete_rule_submit($form, &$form_state) {

  $ruleid = $form_state['values']['ruleid'];

  field_dynamic_delete_rule($ruleid);

  module_invoke_all('field_validation', 'rule', 'delete', $ruleid);

}


Drupal版本:

27 确认表单的使用

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

admin.inc文件,负责的是Field validationUI界面,用来向字段添加规则、编辑规则、删除规则、浏览规则的。这里面有几点可供借鉴,一个是确认表单的使用:

return confirm_form($form,

    t('Are you sure you want to delete the rule %name?', array('%name' => $rule['rulename'])),

    isset($_GET['destination']) ? $_GET['destination'] : $_GET['q'],

    t('This action cannot be undone.'),

    t('Delete'),

    t('Cancel')

  );

通常在删除一个东西的时候,使用这个确认表单,当用户执行一个动作,而这个动作又是不可以撤销的情况下,我们可以让用户确认一下,这个时候如果用户是不小心点到的,看到警告信息,可能就会取消操作。


Drupal版本:

28 添加/编辑表单合二为一

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

另外就是添加/编辑验证规则的表单,使用了同一个函数field_validation_manage_rule,只不过在这个函数内部,做了判断。这样的话,便可以达到复用代码的作用,因为添加和编辑表单两者之间,相似度是非常大的,在Drupal内部,将两者合并是最常见的用法。我以前写程序的时候,最开始开发Drupal的时候,总是将两者分开写。

另外就是theme(‘table’)的使用,输出一个表格也没有什么难的,这里提供了一个具体的例子。


Drupal版本:

29 补充说明

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

代码里面问题很多,我把这样的代码放到了drupal.org上面去以后,有人用过以后,给出了大量的改进意见,还有人提出了赤裸裸的批评。不过alpha1里面的代码,是可以工作的,很好的解决了我们实际中的问题。

其实阅读别人的代码是一个很痛苦的事情,上来我就放了这么多的代码,我想很多人都不愿读下去了。学Drupal也一样,只有足够的代码阅读量以后,写起模块才会得心应手。我读过Drupal5的源代码、Drupal6的源代码,Ubercart的源代码,Commerce的源代码,Views的源代码,Drupal7的大部分源代码。所以对于学Drupal的人,想以这个技能为生的人,建议找一些优秀的模块,读读别人写的代码。读一读自己的同事写的代码。

读完field validation的代码以后,再读读webform validation的代码,别骂我无耻啊,很多代码,基本上都是原封不动的复制过来的。我仅仅把webfrom替换成为了field而已。不过我还是有点自知之明的,在field validation的项目页面,介绍了这些代码的出处,直接说明了,是从webform validation模块里面复制过来的。也算是对webform validation模块作者的一种尊敬。我觉得这一点,至少比国内的很多人,我讲的那些抄袭过后连名字都改了的人,好很多。

Alpha1版,带着众多的问题,问世了,那个时候,drupal.org正在从CSV转到GIT,我对GIT的操作不熟悉,创建了项目页面以后,直接在issue里面传上了代码。后来在小白的帮助下,才学会使用GIT


Drupal版本:

3 info文件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

我们来看一下,最初的代码,这是info文件的:

name = Field Validation

description = "Add validation rules to fields."

core = 7.x

dependencies[] = field

dependencies[] = field_ui

files[] = field_validation.admin.inc

files[] = field_validation.install

files[] = field_validation.module

files[] = field_validation.rules.inc

files[] = field_validation.validators.inc

 

这里面的键值的含义,在第一集里面已经讲过了。我这里补充一点,下面的这段代码:

files[] = field_validation.admin.inc

files[] = field_validation.validators.inc

是多余的。Drupal7里面有个注册表机制,计划解决缓加载这个问题,但是在Drupal7正式发布的时候,这个功能只实现了一小部分,通常只有对那些带有PHP类、接口的文件,使用这里的files键,用来缓加载它们。这是一个很有意思的问题。以前讲过,后面还会讲到,缓加载。


Drupal版本:

30 Field Validation Beta1版的改进

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

很多人提出了很多的问题,其中一个很简单,就是代码里面带有调试信息,相关问题可以参看http://drupal.org/node/1157324,问题的提出者是Lullabotericduran。我按照他提交的补丁,去除了调试信息,当然,还删除了很多从webform_validation复制过来的无用的函数。


Drupal版本:

32 删除无用的函数

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

validators.inc里面删除了函数:

_field_validation_flatten_array

field_validation_valid_component_types

_field_validation_all_allowed

_field_validation_i18n_error_message

_field_validation_check_false

_field_numeric_check_data

 

field_validation.rules.inc文件里面删除了:

field_validation_rule_components_basic


Drupal版本:

33 使用module_implements替代module_invoke_all

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

axel.rutz还发现了一个bughttp://drupal.org/node/1149684),就是如果向多个字段添加验证规则的话,只有最后一个起作用,我最终是这样解决这个问题的,将field_validation_field_attach_validate里面的代码:

 

$errors[$rule['field_name']][$langcode] = module_invoke_all("field_validation_validate", $rule['validator'], $items, $rule);

替换为:

//module_invoke_all does not work here, so i call it directly.

foreach (module_implements("field_validation_validate") as $module) {

  $function = $module . '_' . "field_validation_validate";

  if (function_exists($function)) {

    $function($rule['validator'], $rule, $langcode, $items,  $errors);

  }

}

 

module_invoke_allmodule_implements的转换,是一个进步,使我认识到module_invoke_all是无法传递引用的。field_validation_field_validation_validate也做了相应的调整,这是调整后的样子:

/**

 * Implements hook_field_validation_validate().

 */

function field_validation_field_validation_validate($validator_name, $rule, $langcode, $items, &$errors) {

  if (!empty($items)) {

    switch ($validator_name) {

      case "regex":

        mb_regex_encoding('UTF-8');

        $regex = $rule['data'];

        foreach ($items as $delta => $item) {

          if ($item['value'] != '' && (!mb_ereg("$regex", $item['value']))) {

    $errors[$rule['field_name']][$langcode][$delta][] = array(

              'error' => 'regex_'.$rule['ruleid'], 

              'message' => t($rule['error_message']),

            );

          }

        }

        break;

    }

  }

}

这里可以传递引用参数&$errors了,这里还有另外一个改进,就是为错误消息使用了t函数。


Drupal版本:

34 补充说明

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

很多人提出了新的功能:

1),验证规则可以导入导出。

2),一个验证规则,可以直接应用到多个字段实例,而不是一个一个的复制。

3),多内置一些正则表达式规则,这样用户就不用输入了。

4),验证规则可以取反。

5),希望能够对字段的数据进行预处理,比如使用trim函数。

6),有人还给出了正则表达式的正确格式,我这里使用的是mb_ereg,很多人开始不习惯。mb_ereg是从webform_validation里面复制过来的,直到前不久(Beta1版发布后的1年后),我才知道PHP里面支持两种格式的正则表达式。

这些新功能在beta1里面并没有实现。在修正了明显的bug以后,我发布了Beta1版。Beta1版里面的改进,就是上面所说的。不久,Lullabotericduran写了一篇文章,专门介绍这个模块,http://www.lullabot.com/articles/module-monday-field-validationLullabot是全球知名的Drupal培训公司,自己编写的模块,能够在他们的首页出现,也是一个莫大的荣誉。随之而来的,是模块被更多的人关注,包括webform_validation的作者svendecabootersvendecabooter有一个想法,就是把表单验证、webform验证、字段验证统一起来。这个想法很好,很早的时候,就有人做了尝试,比如Validation API

Svendecabooter的建议,让我感到诚惶诚恐,他写的webform_validation模块,用户量要远远大于刚出来的Field validation。如果他要写一个涵盖所有验证方面的模块的话,那么Field validation就会被包含到里面,最终也将会被废弃掉,因为大家都采用新的模块了。因此我建议Svendecabooter接管Validation API模块,开发一个Drupal7的版本,来涵盖webform

、字段、表单等验证。Svendecabooter接管了Validation API,但是由于他工作比较忙,一直迟迟没有动工。

Field validation是我的练习自己Drupal技能的模块,那个想凭借着它超越东哥的念头是一致存在的,所以内心深处,是比较不喜欢与Svendecabooter的合作的。


Drupal版本:

35 从Beta2到Beta6

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

Beta1版写好以后,我们在项目中一直用它,但是此后的相当长的时间内,我并没有继续开发Field validation。一直过了将近四个月的时间,到2011年的9月份的时候,才开始进一步的完善Field validation。为什么要等这么久,一个原因是当时项目比较忙,另一个就是面对Field validation引起的关注,自己无所适从。


Drupal版本:

36 Beta2版

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

Field validation的安装量,一直在缓慢的增长着,一直到了当时的9月份,增长的速度才快了起来。这样的增长速度,远远低于我的预期。到了9月份,安装量的增加,让我对Field validation的信心增长了不少。我是这样想的,现在只有一个正则表达式验证器,如果我能够多写几个验证器的话,那么用户量就会增加很多。因此我决定增加验证器的数量。在Beta2版里面,我增加以下验证器:

•Numeric values (optionally specify min and / or max value)(数值,可指定min/max

 •Minimum length(最小长度)

 •Maximum length(最大长度)

 •Minimum number of words(最小字数)

 •Maximum number of words(最大字数)

 •Plain text (disallow tags) (纯文本)

 •Must be empty (Anti-Spam: Hide with CSS) (必须为空)

 •Words blacklist(黑名单)

这些验证器都是从webform_validation里面迁移过来的,对我来说,不用去考虑那些界面英文怎么写了。对应的验证逻辑代码,也比较简单。


Drupal版本:

37 beta3版

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

两周后,我又增加了5个验证器,同时修正了一个bug,发布了beta3。这5个验证器分别为:

•Minimum number of selections required(适用于复选框、下拉选择框,最小选中多少项)

•Maximum number of selections allowed(适用于复选框、下拉选择框,最多选中多少项)

•Exact number of selections required(适用于复选框、下拉选择框,只能选中多少项)

•Unique(唯一性验证)

•Match another field(or entity property)(比如匹配一个字段或者一个属性)

在唯一性验证的代码里面,首先,我们检查这个字段当前的值数组里面,是否有重复的,如果没有的话,通过查询数据库,看数据库里面是否存在重复的,如果有重复的,表示不唯一,否则表示字段值是唯一的。如果我们的当前表单是编辑表单,我们在查询数据库的时候,还需要把当前实体排除在外。注意,用户编辑页面,通过entity_extract_ids获取不到用户的ID,不知道现在Drupal核心修正了这个bug没有,所以我单独的为它写了一个判断语句。

以前人们经常使用unique field模块,来验证字段的惟一性,现在有了Field ValidationUnique验证器,基本上就可以取代这个模块了,而且Field Validation支持所有的实体类型。unique field模块仅支持节点类型。

验证器匹配一个字段或者一个属性也使用了EntityFieldQuery,我对这种查询方式的喜欢,可能就是从编写这两个验证器开始的,在这里我发现了EntityFieldQuery的强大。如果使用db_select,也能够实现同样的功能。但是就会比较麻烦,代码的可读性也不高。


Drupal版本:

38 菜单项中路径的构成不能超过10

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

Beta3里面解决了 无法为评论字段添加验证规则http://drupal.org/node/1297366)这个问题,它的原因是这样的,Drupal中的菜单项,里面定义的路径,最多只能包含10个组成部分,超过了这个限制,就无法正常工作。对于评论字段,为它添加验证的菜单项超过了这个限制,也就是路径包含的部分太多了。

我修改了菜单项$items["$path/fields/%field_ui_menu/validation"]的回调函数,将它改为field_validation_callback_dispatch,在回调函数里面做了判断。

这样,在评论的字段管理界面,路径:

admin/structure/types/manage/article/comment/fields/comment_body/validation/add/min_words

会被看作菜单项$items["$path/fields/%field_ui_menu/validation"]

admin/structure/types/manage/article/comment/fields/comment_body/validation

通过field_validation_callback_dispatch函数,这个路径仍然能够被正确处理。这是一个很有意思的问题,以前是不知道Drupal有这个限制的。


Drupal版本:

39 Beta4版

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

两周后,自己又新增了5个验证器

•Specific value(s) :白名单,所填的值必须是给定的值中的一个

•Require at least one of several fields: 多个字段至少填写其中的一个

•Equal values on multiple fields :多个字段中的值必须相等

•Unique values on multiple fields :多个字段中的值必须不同

•Custom PHP function : 自定义PHP函数验证器


Drupal版本:

4 field_validation.install文件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

这是field_validation.install文件的:

<?php

 

/**

 * @file

 * field_validation installation file

 */

 

/**

 * Implements hook_schema().

 */

function field_validation_schema() {

  $schema['field_validation_rule'] = array(

    'description' => 'Stores rule definitions',

    'fields' => array(

      'ruleid' => array(

        'type' => 'serial',

        'description' => 'Unique identifier for a rule',

        'unsigned' => TRUE,

        'not null' => TRUE,

      ),

      'rulename' => array(

        'type' => 'varchar',

        'description' => 'Name for the rule',

        'not null' => TRUE,

        'default' => '',

        'length' => 255,

      ),

'field_name' => array(

        'type' => 'varchar',

        'length' => 32,

        'not null' => TRUE,

        'default' => ''

      ),

      'entity_type' => array(

        'type' => 'varchar',

        'length' => 32,

        'not null' => TRUE,

        'default' => ''

      ),

      'bundle' => array(

        'type' => 'varchar',

        'length' => 128,

        'not null' => TRUE,

        'default' => ''

      ),

      'validator' => array(

        'type' => 'varchar',

        'description' => 'The validator key',

        'not null' => TRUE,

        'default' => '',

        'length' => 255,

      ),

      'data' => array(

        'type' => 'varchar',

        'description' => 'Additional rule data',

        'not null' => FALSE,

        'length' => 255,

      ),

      'error_message' => array(

        'type' => 'varchar',

        'description' => 'Rule error message',

        'not null' => FALSE,

        'length' => 255,

      ),

    ),

    'primary key' => array('ruleid'),

    'indexes' => array(

      'field_name_bundle' => array('field_name', 'entity_type', 'bundle'),

    ),

  );

 

  return $schema;

}

我们来看install文件,这里面的代码很简单,就是用来创建一个数据库表,为数据库表定义了它可以包含那些列,以及数据库表的主键、索引。中国人的抄袭、模仿的能力是比较强的,我也是中国人麻,直接把webform_validation.install里面的代码复制了过来。当然,我做了取舍。webform_validation里面定义了两个数据库表,而我这里只定义了其中的一个,为什么?为什么我没有允许一个规则可以应用到多个字段上面来,就像webform validation里面的那样,一个规则可以应用到多个webform component(组件)上面来?

如果最初这样设计的话,也是可以的,但是我主要考虑到webform component都是单值的,而字段可以是单值的,也可以是多值的,还可以是复合字段,也就是说字段远比webform component复杂。如果一个规则对应多个字段的话,程序会很复杂。而一个规则对应一个字段的话,程序会简单很多。

后来,有很多人,提出来了,如果一个规则能够支持多个字段,就好了,但是一直都没有实现,因为牵涉到的东西太多。而一个规则应用到一个字段的话,实现以后,只需要复制一下,就可以将它应用到多个字段了。简单的,就是好的。在这个问题上,我的想法是正确的。


Drupal版本:

40 对column的支持

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

另外增加的功能是实现了对column的支持,使得验证器可以作用于所有的字段类型,至少从理论上面来说是这样的。我们访问一个字段的值,通常是这样的:

$node->field_myfield[‘und’][0][‘value’]

但是也有可能是这样的:

$node->field_myterm[‘und’][0][‘tid’]

或者:

$node->field_myuser[‘und’][0][‘uid’]

最后面的中括号里面的value,tid,uid就是这里所说的column,这样一说,相信大家更好理解了。最初实现这个功能的时候,数据库里面的存储结构也使用了column,但是出了问题,后来通过检查,发现,原来columnMYSQL的保留字,所以在schema里面的定义是这样的:

      'col' => array(

        'type' => 'varchar',

        'length' => 32,

        'not null' => TRUE,

        'default' => 'value'

      ),

   我把column调整为了col,当然程序里面的相应代码也做了调整。


Drupal版本:

41 “validate”链接

  作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

在字段管理界面,添加了一个“validate”链接,方便为字段添加验证。这里使用仍然是我们熟悉的hook_form_FORM_ID_alter, 当然这里面用到的一些API函数,可能很多人都不熟悉,我也不怎么熟悉,这些代码都是照抄过来的。

Beta4是一个比较经典的版本,到了这里,field validation从功能上已经相当完善了,包含了webform validation上面的所有功能。


Drupal版本:

42 Beta5的问世

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

做开源的东西,并不是完全没有出路,用的人多了,自然就会有人找上门来,Beata4问世不久,有个美国人,找上门来,想让我帮它写一个有关日期范围的验证,这个日期验证,我很早就思考过,当这个需求到来的时候,我便高兴地揽下了这个小活,160美金,美国人也觉得很便宜。这样便有了date validation这个子模块。这个日期时间范围的验证,我自己觉得写的相当漂亮的,而且很通用,美国人的要求,比我写的模块涵盖的功能,要小很多,而我直接写了一个通用的出来。


Drupal版本:

43 使用form_set_error

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

Beta5里面,有一个重大的改变,就是这段代码:

  /*

   $errors[$rule['field_name']][$langcode][$delta][] = array(

              'error' => 'date_range_'.$rule['ruleid'], 

              'message' => t($rule['error_message']),

            );

*/

if($flag){

  $error = $rule['field_name'].']['.$langcode.']['.$delta.']['.$rule['col'];

  $message = t($rule['error_message']);

  form_set_error($error,$message);

}

以前设置错误消息的时候,我总是使用$errors数组,设置,但是它有一个问题,对于emaillink这样的字段不起作用,就是说数组形式,只适用于一部分情况,还有一部分情况不适用。我开始不是很想使用form_set_error,但是这里为了解决这个问题,不得不这样。但是使用了form_set_error以后,又出现了一个新的问题,一个实体表单,嵌套到另一个表单里面的时候,form_set_error就不起作用了,不是不起作用,而是不能完全正确的工作了,为什么?因为这里我们假定了 表单元素的名字的结构是这样的:

$error = $rule['field_name'].']['.$langcode.']['.$delta.']['.$rule['col'];

一般情况下,都是正确的,但是在嵌套表单的情况下,就出了问题。有兴趣的,可以看一下field collection的嵌套形式。


Drupal版本:

44 实现hook_module_implements_alter

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.comBeta5module文件里面,还引入了这段代码:

/**

 * Implements hook_module_implements_alter().

 * 

 * Ensures the call to field_validation_form_field_ui_field_overview_form_alter()

 * function runs after any invocation of the form_alter() by other modules, e.g.

 * Field Group module.

 */

function field_validation_module_implements_alter(&$implementations, $hook) {

  if ($hook == 'form_alter' && array_key_exists('field_validation', $implementations)) {

    $group = $implementations['field_validation'];

    unset($implementations['field_validation']);

    $implementations['field_validation'] = $group;

  }

}

这段代码的作用是,让field_validation模块的钩子hook_form_alter实现,放在最后面执行,这样就不会和field group模块冲突。

我们经常会遇到这样的问题,多个模块同时实现了一个钩子,此时执行的顺序,有时候非常重要,比如hook_form_alter就属于这样的钩子。在以前的情况下,我们采用修改模块重量的方式,简单一些的话,就是直接使用phpmyadmin,打开数据库的system表,直接修改对应模块的重量。复杂一点的话,把它放到安装文件的hook_install钩子里面。

/**

 * Implements hook_install().

 */

function field_validation_install() {

  db_query("UPDATE {system} SET weight = 10 WHERE name = 'field_validation'");

}

    这里顺带说一下,在2.X版本里面,我们实现了对Feeds的集成,实现了hook_feeds_processor_targets_alter这个钩子,但是我们的实现,和Feeds模块自身的实现冲突了,所以我调整了一下模块的重量,通过调整重量,使得程序能够正常工作了.


Drupal版本:

45 Beta6是一个过渡

 作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

里面只包含一些代码格式的修正,我自己编写的代码,开始的时候,很不符合Drupal的编码规范,在beta6里面做了改进。为什么要发布beta6呢,因为我想把当前的一些改进做一个备份,接下来要实现验证规则的导入导出。


Drupal版本:

5 module文件

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

这是module文件的代码:

<?php

 

/**

 * @file

 * Add validation rules to webforms

 */

 

include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'field_validation') . '/' . 'field_validation.validators.inc';

include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'field_validation') . '/' . 'field_validation.rules.inc';

 

/**

 * Implements hook_menu().

 */

function field_validation_menu() {

  $items = array();

 

foreach (entity_get_info() as $entity_type => $entity_info) {

    if ($entity_info['fieldable']) {

      foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {

        if (isset($bundle_info['admin'])) {

          // Extract path information from the bundle.

          $path = $bundle_info['admin']['path'];

          if (isset($bundle_info['admin']['bundle argument'])) {

            $bundle_arg = $bundle_info['admin']['bundle argument'];

            $bundle_pos = (string) $bundle_arg;

          }

          else {

            $bundle_arg = $bundle_name;

            $bundle_pos = '0';

          }

          // This is the position of the %field_ui_menu placeholder in the

          // items below.

          $field_position = count(explode('/', $path)) + 1;

 

          // Extract access information, providing defaults.

          $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));

          $access += array(

            'access callback' => 'user_access',

            'access arguments' => array('administer site configuration'),

          );

 

          $items["$path/fields/%field_ui_menu/validation"] = array(

            'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),

            'title' => 'Validation',

            'page callback' => 'field_validation_manage',

            'page arguments' => array($field_position),

            'type' => MENU_LOCAL_TASK,

            'file' => 'field_validation.admin.inc',

          ) + $access;


          $items[$path.'/fields/%field_ui_menu/validation/add'] = array(

            'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),

            'title' => 'Add validation',

'page callback' => 'drupal_get_form',

'page arguments' => array('field_validation_manage_rule', $field_position, 'add'),

            'type' => MENU_CALLBACK,

            'file' => 'field_validation.admin.inc',

          ) + $access;


$items["$path/fields/%field_ui_menu/validation/edit"] = array(

            'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),

            'title' => 'Edit rule',

'page callback' => 'drupal_get_form',

'page arguments' => array('field_validation_manage_rule', $field_position, 'edit'),

            'type' => MENU_CALLBACK,

            'file' => 'field_validation.admin.inc',

          ) + $access;


$items["$path/fields/%field_ui_menu/validation/delete"] = array(

            'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),

            'title' => 'Delete rule',

'page callback' => 'drupal_get_form',

'page arguments' => array('field_validation_delete_rule', $field_position+3),

            'type' => MENU_CALLBACK,

            'file' => 'field_validation.admin.inc',

          ) + $access;

        }

      }

    }

  }


  return $items;

}

 

/**

 * Implements hook_theme().

 */

function field_validation_theme() {

  return array(

    'field_validation_manage_add_rule' => array(

      'variables' => array(

        'instance' => NULL,

      ),

    ),

    'field_validation_manage_overview' => array(

      'variables' => array(

        'rules' => NULL,

        'instance' => NULL,

      ),

    ),

  );

}

 

/**

 * Implements hook_field_attach_validate().

 */

function field_validation_field_attach_validate($entity_type, $entity, &$errors) {

  //ToDo;

list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);

$rules = field_validation_get_bundle_rules($entity_type, $bundle);

if($rules){

foreach ($rules as $rule) {

$field_name = $rule['field_name'];

      $field = field_info_field($field_name);

  $languages = field_available_languages($entity_type, $field);

foreach ($languages as $langcode) {

  $items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();

$errors[$rule['field_name']][$langcode] = module_invoke_all("field_validation_validate", $rule['validator'], $items, $rule);

}


}

  }

}

 

/**

 * Theme the 'add rule' list

 */

function theme_field_validation_manage_add_rule($variables) {

  $instance = $variables['instance'];

  $output = '';

  $validators = field_validation_get_validators();

 

  if ($validators) {

    $output = '<h3>' . t('Add a validation rule') . '</h3>';

    $output .= '<dl>';

    foreach ($validators as $validator_key => $validator_info) {

      $item = '';

  $path = isset($_GET['q']) ? $_GET['q'] : '';

      $url = $path.'/add/' . $validator_key;

      $components = ' (' . implode(', ', $validator_info['component_types']) . ')';

      $item = '<dt>' . l($validator_info['name'], $url, array("query" => drupal_get_destination())) . '</dt>';

      $item .= '<dd>';

      if ($validator_info['description']) {

        $item .= $validator_info['description'] . ' ';

      }

      $item .= $components . '</dd>';

      $output .= $item;

    }

    $output .= '</dl>';

  }

  return $output;

}

 

/**

 * Implements hook_field_delete().

 */

function field_validation_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) {

  $rules = field_validation_get_field_rules($instance);

  if ($rules) {

    foreach (array_keys($rules) as $ruleid) {

      field_dynamic_delete_rule($ruleid);

    }

  }

}

 

/**

 * Save a validation rule. Data comes from the admin form

 * or nodeapi function in case of node clone

 */

function field_validation_rule_save($values) {

  // save rules data

  if ($values['action'] == 'add') {

    drupal_write_record('field_validation_rule', $values);

    $ruleid = $values['ruleid'];

    if ($ruleid) {

      module_invoke_all('field_validation', 'rule', 'add', $values);

    }

  }

 

  if ($values['action'] == 'edit') {

    drupal_write_record('field_validation_rule', $values, 'ruleid');

    $ruleid = $values['ruleid'];

    if ($ruleid) {

      module_invoke_all('field_validation', 'rule', 'edit', $values);

    }

  }

}


Drupal版本:

6 field_validation_field_attach_validate

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

这里面,最先实现的是field_validation_field_attach_validate,在这个钩子函数中,alpha1版本里面包含两大段注释代码,最下面的那段注释代码,就是我最初测试hook_field_attach_validate所用到的代码,在这个版中还保留着,幸好还保留着,这样可以留下一个成长的轨迹。最上面那段注释里面的代码,是最初的实现代码。把它的格式整理一下:

  // 获取一个bundle上面所有的字段实例.

  $instances = field_info_instances($entity_type, $bundle);

  //debug($instances);

  // 对每个字段实例进行迭代处理,

  foreach ($instances as $instance) {

    $field_name = $instance['field_name'];

    $field = field_info_field($field_name);

    // 获取字段上面可用的语言.

    $languages = field_available_languages($entity_type, $field);

    //对于每个语言进行迭代

    foreach ($languages as $langcode) {

      $items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();

      debug($items);

      //查找这个字段实例上面,是否存在验证规则,如果存在验证规则,执行这些验证

      $rules = field_validation_get_field_rules($instance);

      if($rules){

        foreach ($rules as $rule) {

          module_invoke_all("field_validation_validate", $rule['validator'], $items, $rule, $errors);

        }

      }

    }

  }

这段代码也是可以工作的,为什么把它注释掉呢?因为我找到了更好的办法了。没有被注释掉的代码,就是更好的办法。在注释掉的代码中,我们首先获取这个实体上面都有哪些字段,然后对字段循环处理,分别检查每个字段上面是否存在验证规则,如果存在,再进行验证。   

实际上,大部分的字段上面都没有验证规则的,根据字段循环迭代,浪费了。改进的代码,逻辑是这样的,检查这个实体类型上面是否存在验证规则,如果存在,分别对这些规则进行验证,有多少规则验证多少,没有的话,跳过去。具体的代码实现,在后面多次的改进,变更,但是这个基本逻辑,却被保留了下来。这就是我们前面看到的代码,我这里加点注释说明:

 

  //获取bundle上面的所有验证规则,这个函数位于field_validation.rules.inc中,

//是最早实现的函数之一

  $rules = field_validation_get_bundle_rules($entity_type, $bundle);

  if($rules){

    //对于每个规则循环处理

    foreach ($rules as $rule) {

      $field_name = $rule['field_name'];

      $field = field_info_field($field_name);

      $languages = field_available_languages($entity_type, $field);

      foreach ($languages as $langcode) {

        $items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();

//这里面仍然使用了module_invoke_all,但是这里是有返回值的,

//我们将返回值赋值给了$errors[$rule['field_name']][$langcode]

//这与最初的实现不同,最初的实现里面,是把$errors传递了过去,

//不过module_invoke_all是无法传递引用的,因此无法正常工作

//我经过调试,调试才通过这个办法解决了这个问题,当然这里还是有问题的

        $errors[$rule['field_name']][$langcode] = module_invoke_all("field_validation_validate", $rule['validator'], $items, $rule);

      }

    }

  }

我开始的时候,是没有意识到module_invoke_all无法传递引用的,这也是为什么我列出field_attach_validate这个API函数源代码的原因,里面的注释,说明了这一点,但是最初的时候,自己没有看注释,或者看的不仔细。实际调试的过程中,才发现问题,最后才恍然大悟。

我在这里,第一次实现了自己的Hook,这是我至今都值得骄傲的事情。很久以前,就想自己定义钩子,一直没有机会。尽管这里的钩子,是借鉴hook_webform_validation_validate而来的,但是终归也是自己第一次定义一个钩子函数,并且后来,还有其他开发者使用了这个钩子函数。


Drupal版本:

7 include_once

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

最上面的两个include_once,用来将文件field_validation.validators.incfield_validation.rules.inc加载进来,当加载field_validation.module文件的时候,就会自动的加载这两个文件。为什么要把它分成多个文件呢?这样做唯一的好处就是,逻辑更清楚一点,比如field_validation.validators.inc只放置验证器的定义。它并不能实现缓加载,也并不能提升性能。如果我们把field_validation.validators.inc里面的函数,直接放到module文件中,效果是一样的。很多模块都采用这种方式,或许有的人认为,它能够实现缓加载,但是实际是不行的。


Drupal版本:

8 field_validation_menu

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

field_validation_menu里面,最上面的那段代码,是我从field_ui模块里面复制过来的,最后实现UI的时候,考虑到字段的配置,它的路径不是固定的,不同实体类型的路径是不同的,而我的验证规则,是追加到字段上面去的,所以只好从field_ui模块复制代码。如果让我自己写,我是写不出来的。这里只需要理解就可以了,知道这段代码的出处。通过Hook_menu,我们为每个字段提供了一个验证概览页面,一个验证规则的添加页面,一个验证规则的编辑页面,还有验证规则的删除页面。除了这里的路径是动态的以外,并不比我们见到的其它菜单项复杂。


Drupal版本:

9 field_validation_theme

作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com

field_validation_theme这个钩子函数,是直接从webform_validation_theme抄袭过来的,里面注册的主题函数,也是改了一下名字而已。theme_field_validation_manage_add_rule,这个主题函数,也是重命名过来的,但是函数里面的内容,我做了稍微的调整,其实这个函数只在管理界面用到,放到field_validation.admin.inc文件里面更合适一点。因为theme_field_validation_manage_overview也是放在field_validation.admin.inc里面的。theme_field_validation_manage_add_rule里面有两段代码值得学习,一个是获取当前路径:

$path = isset($_GET['q']) ? $_GET['q'] : '';

另一个是生成一个链接,这里向链接传递了更多的选项:

l($validator_info['name'], $url, array("query" => drupal_get_destination()))


Drupal版本: