CodeIgniter callback 表单验证中如何正确比较日期
结论
CodeIgniter 表单验证 callback 会把当前字段的值作为第一个参数传入,验证规则方括号中的 $projectID 是第二个参数。
因此,callback 应定义为:
public function comparePaymentDate($paymentDate, $projectID)
不能只定义为:
public function comparePaymentDate($projectID)
另外,getLatestPayment() 返回的是数组,比较日期时应使用 $latestPayment['payment_date']。有效日期需要满足:
last payment date < payment date <= today
如果输入日期小于或等于最后付款日期,或者晚于今天,就应返回 FALSE。
正确的 callback 写法
如果表单提交的日期格式是 Y-m-d,可以通过 DateTime::createFromFormat() 严格解析:
public function comparePaymentDate($paymentDate, $projectID)
{
$latestPayment = $this->payment_model->getLatestPayment($projectID);
$inputDate = DateTime::createFromFormat('!Y-m-d', $paymentDate);
$today = new DateTime('today');
if (!$inputDate) {
$this->form_validation->set_message(
'comparePaymentDate',
'Payment Date is invalid.'
);
return FALSE;
}
// 如果还没有付款记录,只需要检查输入日期不能晚于今天
if (empty($latestPayment) || empty($latestPayment['payment_date'])) {
if ($inputDate > $today) {
$this->form_validation->set_message(
'comparePaymentDate',
'Payment Date should be earlier than or equal to the date today.'
);
return FALSE;
}
return TRUE;
}
$lastPaymentDate = DateTime::createFromFormat(
'!Y-m-d',
$latestPayment['payment_date']
);
if (!$lastPaymentDate) {
log_message('error', 'Invalid payment_date stored for project ID: ' . $projectID);
$this->form_validation->set_message(
'comparePaymentDate',
'Unable to validate Payment Date.'
);
return FALSE;
}
if ($inputDate <= $lastPaymentDate || $inputDate > $today) {
$this->form_validation->set_message(
'comparePaymentDate',
'Payment Date should be later than the latest payment date and earlier than or equal to today.'
);
return FALSE;
}
return TRUE;
}
验证规则仍可通过方括号传入项目 ID:
array(
'field' => 'form_payment_date',
'label' => 'Payment Date',
'rules' => 'required|callback_comparePaymentDate[' . $projectID . ']'
)
CodeIgniter 执行 callback 时,实际传入的参数相当于:
comparePaymentDate(
$this->input->post('form_payment_date'),
$projectID
);
所以,原来的方法只有一个参数 comparePaymentDate($projectID),这个参数实际接收到的是表单日期,而不是项目 ID。
使用时间戳的简化写法
如果数据库和表单中的日期都是明确的 Y-m-d 格式,也可以改用时间戳比较:
public function comparePaymentDate($paymentDate, $projectID)
{
$latestPayment = $this->payment_model->getLatestPayment($projectID);
$inputTimestamp = strtotime($paymentDate);
$todayTimestamp = strtotime(date('Y-m-d'));
if ($inputTimestamp === FALSE) {
$this->form_validation->set_message(
'comparePaymentDate',
'Payment Date is invalid.'
);
return FALSE;
}
if (!empty($latestPayment['payment_date'])) {
$lastPaymentTimestamp = strtotime($latestPayment['payment_date']);
if (
$lastPaymentTimestamp === FALSE ||
$inputTimestamp <= $lastPaymentTimestamp ||
$inputTimestamp > $todayTimestamp
) {
$this->form_validation->set_message(
'comparePaymentDate',
'Payment Date should be later than the latest payment date and earlier than or equal to today.'
);
return FALSE;
}
} elseif ($inputTimestamp > $todayTimestamp) {
$this->form_validation->set_message(
'comparePaymentDate',
'Payment Date should be earlier than or equal to the date today.'
);
return FALSE;
}
return TRUE;
}
查询最后付款日期
如果“最后付款”是指 payment_date 最大的记录,应直接按 payment_date 排序,不要按 payment_id 排序:
public function getLatestPayment($projectID)
{
return $this->db
->select('payment_date')
->from('payment')
->where('project_id', $projectID)
->order_by('payment_date', 'DESC')
->limit(1)
->get()
->row_array();
}
只有当 payment_id 的顺序能够可靠反映付款发生顺序时,才适合按 payment_id 排序。
日期格式注意事项
5/2/2019 是有歧义的,既可能表示 2019 年 5 月 2 日,也可能表示 2019 年 2 月 5 日。前端和后端应统一日期格式,最好提交没有歧义的 Y-m-d:
2019-05-02
如果表单提交的确实是 m/d/Y,解析格式也要相应调整:
$inputDate = DateTime::createFromFormat('!m/d/Y', $paymentDate);
不要让 strtotime() 猜测有歧义的日期格式。以输入日期 2019-05-02、最后付款日期 2019-05-05 为例,下面的条件成立:
$inputDate <= $lastPaymentDate
因此,callback 会返回 FALSE。
备注:内容仅供参考。
