作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
我们第4集主要讲解开发与代码,当然很多代码是在前面的网上书店系统里面都用到的,部分代码是对网上书店原有功能的补充。此外,他不是一个严格意义上面的参考书,但是希望这份资料对大家仍然有用。
我们先来看一个简单的,这个对我来说很简单的,因为代码已经写好了,还放到了网络上了,这就是我写的Field validation模块。我们这一章,主要讲解field validation的演化历程,讲解里面代码的变化,用到的Drupal7的相关技术。如果让我凭空写出来一个模块进行讲解,效果未必会怎么好。
其实很久很久以前,我一直都有一个愿望,就是在drupal.org上贡献几个模块,而且这几个模块还比较流行,这一直是我的心愿。从最初的uc_alipay,到block_morelink,两个模块的用户量都是比较小的,uc_alipay我觉得怎么也能发展到5,6百的安装量,但是实际只有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刚刚出来,很多模块都没有跟上。这是一个机会。
作者:老葛,北京亚艾元软件有限责任公司,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函数,便可以提取出来它的id、vid、bundle。这是很有用的,但是有时候,这个API函数并不能正常工作,个别时候,大部分都是可以的。
此外,这个错误消息的设置:
$errors['field_text']['und'][0][] = array(
'error' => 'field_text',
'message' => t('length must less than 8.'),
);
这个,我们在第一集的,字段API一章里面介绍过了这个结构。我最初编写的时候,并不知道这两个键的含义,尤其是'error',所以默认把它设置为了字段名字。'message'的含义我是清楚的,用来设置具体的错误消息。
代码并不是一下子就工作了,我还做了很多的调试工作。还是使用原始的drupal_set_message和debug。
有了hook_field_attach_validate,我们便不需要考虑具体的表单ID了,因为内容类型很多,实体类型也很多。我们不可能处理所有的情况,而hook_field_attach_validate可以帮助我们从这个问题中解脱出来。我在选用这个钩子以前,应该读过Field Attach API,http://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上面搜索相关的钩子函数:
它的触发函数,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);
}
}
我读的时候不够细致,忽略里面的注释。总之,我做了很多的准备工作的。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
field_validation_field_delete是hook_field_delete的钩子实现,这段代码的作用是,当删除一个字段时,删除字段上面的验证规则。这里面用到的field_dynamic_delete_rule函数,命名不符合Drupal的规范,最好命名为field_validation_dynamic_delete_rule。field_validation_field_delete和field_dynamic_delete_rule的名字也都是来源于webform_validation的。在webform_validation里面,使用的钩子函数是hook_node_delete,当一个节点被删除时,删除节点上面的验证规则,其实我觉得webform_validation里面应该检查一下这个节点的类型是不是webform形式的,不是的话直接跳过,这样效率会更高一点。
作者:老葛,北京亚艾元软件有限责任公司,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的代码。如果让我自己写,是写不出来这样的代码的。
作者:老葛,北京亚艾元软件有限责任公司,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_validators,field_validation_field_validation_validate,field_validation_get_validators是有用的。其它的大部分都是特定于webform_validation的,当时是一股脑的复制了过来的,然后一个一个的字符串替换。我们看到这里保留了大量的webform_validation的信息。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
field_validation_field_validation_validators是一个钩子函数,实现的就是我们自己定义的钩子hook_field_validation_validators,它类似于hook_menu,返回的是一个数组,在这个数组里面,可以定义多个验证器,我们这里只定义了一个regex,正则表达式。在单个验证器数组里面,它包含5个键,name、component_types、custom_error、custom_data、description。都是直接来自于webform_validation的。我在这里出于方便的考虑,做了取舍,比如每个验证器都是需要自定义错误消息的,而在webform_validation里面,只有部分验证器可以自定义错误消息,为什么?其中一个原因就是,这些错误消息,需要用到英文,如果将来定义的验证器类型比较多的话,需要写很多这样的英文,自己的英文读还可以,写就不行了,怕出问题;另外一个就是,如果用英文的,还需要翻译,这对很多非英文的用户不方便,特别是中文用户;出于这两点考虑,我全部采用了自定义消息。这里的component_types其实应该改为field_types,表示这个验证器适用于哪些字段类型,实际上,我在程序里面,省去了这一检查,就是说一个验证器适用于所有的字段类型,这也是一个改进。在webform_validation,只有适用于一个组件类型的验证器,才能应用到该组件上,webform的组件类型比较少,主要定义在webform模块里面,而field的类型,除了Drupal核心自带的,很多第三方模块还定义了很多。我希望一个验证器可以应用于所有的字段类型,这一点上面不加限制,至于是否真的适用,由用户自己决定,而不是交给程序,写死在这里。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
field_validation_field_validation_validate这也是一个钩子函数的实现,。这里面包含了验证器的验证规则。当传递过来的数据没有通过正则验证时,我们返回一个包含错误信息的数组。这个错误消息,应该通过引用传递的,但是module_invoke_all不支持引用传递。所以只好这样变通一下
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
函数field_validation_get_validators用来获取所有的字段验证器,这里再次使用了module_invoke_all,hook_field_validation_validators就是在这里定义的,这里还使用了:
drupal_alter('field_validator', $validators);
这样其它模块就可以通过钩子hook_field_validator_alter来修改验证器定义了。
field_validation_get_validators_selection和field_validation_get_validator_info这两个函数还是有一点用的,不过只在field_validation.admin.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。
作者:老葛,北京亚艾元软件有限责任公司,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);
}
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
admin.inc文件,负责的是Field validation的UI界面,用来向字段添加规则、编辑规则、删除规则、浏览规则的。这里面有几点可供借鉴,一个是确认表单的使用:
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')
);
通常在删除一个东西的时候,使用这个确认表单,当用户执行一个动作,而这个动作又是不可以撤销的情况下,我们可以让用户确认一下,这个时候如果用户是不小心点到的,看到警告信息,可能就会取消操作。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
另外就是添加/编辑验证规则的表单,使用了同一个函数field_validation_manage_rule,只不过在这个函数内部,做了判断。这样的话,便可以达到复用代码的作用,因为添加和编辑表单两者之间,相似度是非常大的,在Drupal内部,将两者合并是最常见的用法。我以前写程序的时候,最开始开发Drupal的时候,总是将两者分开写。
另外就是theme(‘table’)的使用,输出一个表格也没有什么难的,这里提供了一个具体的例子。
作者:老葛,北京亚艾元软件有限责任公司,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 validation的7.x-1.0版。我最初的代码,很多都是从webform validation里面复制过来的。
首先,我从头到尾的完整的读过webform validation的代码,觉得很不错,至少那个时候,觉得这个已经相当复杂了。同时,我还测试过webform validation模块的具体用法。但是将webform validation改造成field_validation的过程也是相当的痛苦的,调试,调试再调试。大致的过程是这样的,首先是数据库设计,也就是install文件,这个可以工作;接着是自己手工的在数据库里面,插入一个验证规则,然后检查这个验证规则,能够正常工作;最后是编写规则的配置界面。第一步比较简单,第二步是关键,第三步就是规则的增删改查,一个体力活而已,但是调试起来比较麻烦。
作者:老葛,北京亚艾元软件有限责任公司,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。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
很多人提出了很多的问题,其中一个很简单,就是代码里面带有调试信息,相关问题可以参看http://drupal.org/node/1157324,问题的提出者是Lullabot的ericduran。我按照他提交的补丁,去除了调试信息,当然,还删除了很多从webform_validation复制过来的无用的函数。
作者:老葛,北京亚艾元软件有限责任公司,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
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
axel.rutz还发现了一个bug(http://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_all到module_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函数。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
很多人提出了新的功能:
(1),验证规则可以导入导出。
(2),一个验证规则,可以直接应用到多个字段实例,而不是一个一个的复制。
(3),多内置一些正则表达式规则,这样用户就不用输入了。
(4),验证规则可以取反。
(5),希望能够对字段的数据进行预处理,比如使用trim函数。
(6),有人还给出了正则表达式的正确格式,我这里使用的是mb_ereg,很多人开始不习惯。mb_ereg是从webform_validation里面复制过来的,直到前不久(Beta1版发布后的1年后),我才知道PHP里面支持两种格式的正则表达式。
这些新功能在beta1里面并没有实现。在修正了明显的bug以后,我发布了Beta1版。Beta1版里面的改进,就是上面所说的。不久,Lullabot的ericduran写了一篇文章,专门介绍这个模块,http://www.lullabot.com/articles/module-monday-field-validation。Lullabot是全球知名的Drupal培训公司,自己编写的模块,能够在他们的首页出现,也是一个莫大的荣誉。随之而来的,是模块被更多的人关注,包括webform_validation的作者svendecabooter,svendecabooter有一个想法,就是把表单验证、webform验证、字段验证统一起来。这个想法很好,很早的时候,就有人做了尝试,比如Validation API。
Svendecabooter的建议,让我感到诚惶诚恐,他写的webform_validation模块,用户量要远远大于刚出来的Field validation。如果他要写一个涵盖所有验证方面的模块的话,那么Field validation就会被包含到里面,最终也将会被废弃掉,因为大家都采用新的模块了。因此我建议Svendecabooter接管Validation API模块,开发一个Drupal7的版本,来涵盖webform
、字段、表单等验证。Svendecabooter接管了Validation API,但是由于他工作比较忙,一直迟迟没有动工。
Field validation是我的练习自己Drupal技能的模块,那个想凭借着它超越东哥的念头是一致存在的,所以内心深处,是比较不喜欢与Svendecabooter的合作的。
作者:老葛,北京亚艾元软件有限责任公司,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。
作者:老葛,北京亚艾元软件有限责任公司,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);
}
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
admin.inc文件,负责的是Field validation的UI界面,用来向字段添加规则、编辑规则、删除规则、浏览规则的。这里面有几点可供借鉴,一个是确认表单的使用:
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')
);
通常在删除一个东西的时候,使用这个确认表单,当用户执行一个动作,而这个动作又是不可以撤销的情况下,我们可以让用户确认一下,这个时候如果用户是不小心点到的,看到警告信息,可能就会取消操作。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
另外就是添加/编辑验证规则的表单,使用了同一个函数field_validation_manage_rule,只不过在这个函数内部,做了判断。这样的话,便可以达到复用代码的作用,因为添加和编辑表单两者之间,相似度是非常大的,在Drupal内部,将两者合并是最常见的用法。我以前写程序的时候,最开始开发Drupal的时候,总是将两者分开写。
另外就是theme(‘table’)的使用,输出一个表格也没有什么难的,这里提供了一个具体的例子。
作者:老葛,北京亚艾元软件有限责任公司,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。
作者:老葛,北京亚艾元软件有限责任公司,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键,用来缓加载它们。这是一个很有意思的问题。以前讲过,后面还会讲到,缓加载。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
很多人提出了很多的问题,其中一个很简单,就是代码里面带有调试信息,相关问题可以参看http://drupal.org/node/1157324,问题的提出者是Lullabot的ericduran。我按照他提交的补丁,去除了调试信息,当然,还删除了很多从webform_validation复制过来的无用的函数。
作者:老葛,北京亚艾元软件有限责任公司,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
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
axel.rutz还发现了一个bug(http://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_all到module_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函数。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
很多人提出了新的功能:
(1),验证规则可以导入导出。
(2),一个验证规则,可以直接应用到多个字段实例,而不是一个一个的复制。
(3),多内置一些正则表达式规则,这样用户就不用输入了。
(4),验证规则可以取反。
(5),希望能够对字段的数据进行预处理,比如使用trim函数。
(6),有人还给出了正则表达式的正确格式,我这里使用的是mb_ereg,很多人开始不习惯。mb_ereg是从webform_validation里面复制过来的,直到前不久(Beta1版发布后的1年后),我才知道PHP里面支持两种格式的正则表达式。
这些新功能在beta1里面并没有实现。在修正了明显的bug以后,我发布了Beta1版。Beta1版里面的改进,就是上面所说的。不久,Lullabot的ericduran写了一篇文章,专门介绍这个模块,http://www.lullabot.com/articles/module-monday-field-validation。Lullabot是全球知名的Drupal培训公司,自己编写的模块,能够在他们的首页出现,也是一个莫大的荣誉。随之而来的,是模块被更多的人关注,包括webform_validation的作者svendecabooter,svendecabooter有一个想法,就是把表单验证、webform验证、字段验证统一起来。这个想法很好,很早的时候,就有人做了尝试,比如Validation API。
Svendecabooter的建议,让我感到诚惶诚恐,他写的webform_validation模块,用户量要远远大于刚出来的Field validation。如果他要写一个涵盖所有验证方面的模块的话,那么Field validation就会被包含到里面,最终也将会被废弃掉,因为大家都采用新的模块了。因此我建议Svendecabooter接管Validation API模块,开发一个Drupal7的版本,来涵盖webform
、字段、表单等验证。Svendecabooter接管了Validation API,但是由于他工作比较忙,一直迟迟没有动工。
Field validation是我的练习自己Drupal技能的模块,那个想凭借着它超越东哥的念头是一致存在的,所以内心深处,是比较不喜欢与Svendecabooter的合作的。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
Beta1版写好以后,我们在项目中一直用它,但是此后的相当长的时间内,我并没有继续开发Field validation。一直过了将近四个月的时间,到2011年的9月份的时候,才开始进一步的完善Field validation。为什么要等这么久,一个原因是当时项目比较忙,另一个就是面对Field validation引起的关注,自己无所适从。
作者:老葛,北京亚艾元软件有限责任公司,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里面迁移过来的,对我来说,不用去考虑那些界面英文怎么写了。对应的验证逻辑代码,也比较简单。
作者:老葛,北京亚艾元软件有限责任公司,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 Validation的Unique验证器,基本上就可以取代这个模块了,而且Field Validation支持所有的实体类型。unique field模块仅支持节点类型。
验证器“匹配一个字段或者一个属性”也使用了EntityFieldQuery,我对这种查询方式的喜欢,可能就是从编写这两个验证器开始的,在这里我发现了EntityFieldQuery的强大。如果使用db_select,也能够实现同样的功能。但是就会比较麻烦,代码的可读性也不高。
作者:老葛,北京亚艾元软件有限责任公司,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有这个限制的。
作者:老葛,北京亚艾元软件有限责任公司,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函数验证器
这是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复杂。如果一个规则对应多个字段的话,程序会很复杂。而一个规则对应一个字段的话,程序会简单很多。
后来,有很多人,提出来了,如果一个规则能够支持多个字段,就好了,但是一直都没有实现,因为牵涉到的东西太多。而一个规则应用到一个字段的话,实现以后,只需要复制一下,就可以将它应用到多个字段了。简单的,就是好的。在这个问题上,我的想法是正确的。
作者:老葛,北京亚艾元软件有限责任公司,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,但是出了问题,后来通过检查,发现,原来column是MYSQL的保留字,所以在schema里面的定义是这样的:
'col' => array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => 'value'
),
我把column调整为了col,当然程序里面的相应代码也做了调整。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
在字段管理界面,添加了一个“validate”链接,方便为字段添加验证。这里使用仍然是我们熟悉的hook_form_FORM_ID_alter, 当然这里面用到的一些API函数,可能很多人都不熟悉,我也不怎么熟悉,这些代码都是照抄过来的。
Beta4是一个比较经典的版本,到了这里,field validation从功能上已经相当完善了,包含了webform validation上面的所有功能。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
做开源的东西,并不是完全没有出路,用的人多了,自然就会有人找上门来,Beata4问世不久,有个美国人,找上门来,想让我帮它写一个有关日期范围的验证,这个日期验证,我很早就思考过,当这个需求到来的时候,我便高兴地揽下了这个小活,160美金,美国人也觉得很便宜。这样便有了date validation这个子模块。这个日期时间范围的验证,我自己觉得写的相当漂亮的,而且很通用,美国人的要求,比我写的模块涵盖的功能,要小很多,而我直接写了一个通用的出来。
作者:老葛,北京亚艾元软件有限责任公司,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数组,设置,但是它有一个问题,对于email,link这样的字段不起作用,就是说数组形式,只适用于一部分情况,还有一部分情况不适用。我开始不是很想使用form_set_error,但是这里为了解决这个问题,不得不这样。但是使用了form_set_error以后,又出现了一个新的问题,一个实体表单,嵌套到另一个表单里面的时候,form_set_error就不起作用了,不是不起作用,而是不能完全正确的工作了,为什么?因为这里我们假定了 表单元素的名字的结构是这样的:
$error = $rule['field_name'].']['.$langcode.']['.$delta.']['.$rule['col'];
一般情况下,都是正确的,但是在嵌套表单的情况下,就出了问题。有兴趣的,可以看一下field collection的嵌套形式。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com在Beta5的module文件里面,还引入了这段代码:
/**
* 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模块自身的实现冲突了,所以我调整了一下模块的重量,通过调整重量,使得程序能够正常工作了.
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
里面只包含一些代码格式的修正,我自己编写的代码,开始的时候,很不符合Drupal的编码规范,在beta6里面做了改进。为什么要发布beta6呢,因为我想把当前的一些改进做一个备份,接下来要实现验证规则的导入导出。
作者:老葛,北京亚艾元软件有限责任公司,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);
}
}
}
作者:老葛,北京亚艾元软件有限责任公司,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而来的,但是终归也是自己第一次定义一个钩子函数,并且后来,还有其他开发者使用了这个钩子函数。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
最上面的两个include_once,用来将文件field_validation.validators.inc和field_validation.rules.inc加载进来,当加载field_validation.module文件的时候,就会自动的加载这两个文件。为什么要把它分成多个文件呢?这样做唯一的好处就是,逻辑更清楚一点,比如field_validation.validators.inc只放置验证器的定义。它并不能实现缓加载,也并不能提升性能。如果我们把field_validation.validators.inc里面的函数,直接放到module文件中,效果是一样的。很多模块都采用这种方式,或许有的人认为,它能够实现缓加载,但是实际是不行的。
作者:老葛,北京亚艾元软件有限责任公司,http://www.yaiyuan.com
field_validation_menu里面,最上面的那段代码,是我从field_ui模块里面复制过来的,最后实现UI的时候,考虑到字段的配置,它的路径不是固定的,不同实体类型的路径是不同的,而我的验证规则,是追加到字段上面去的,所以只好从field_ui模块复制代码。如果让我自己写,我是写不出来的。这里只需要理解就可以了,知道这段代码的出处。通过Hook_menu,我们为每个字段提供了一个验证概览页面,一个验证规则的添加页面,一个验证规则的编辑页面,还有验证规则的删除页面。除了这里的路径是动态的以外,并不比我们见到的其它菜单项复杂。
作者:老葛,北京亚艾元软件有限责任公司,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()))