Склонение существительных в текстах ошибок библиотеки Form validation
Материал из Wiki
Смастерил небольшой костыль из разряда «мелочь, а приятно» к библиотеке валидации. Ниже код и описание.
У библиотеки есть малюсенькая проблемка с сообщениями об ошибках с лимитами (минимальное/максимальное количество символов, например): в шаблон можно подставить только число, форму слова (например, «символ») изменить нельзя. В итоге получаем что-то вроде «Поле Пароль должно содержать не меньше 1 символов». Ерунда, конечно, но хочется писать правильно.
Прежде всего, понадобится функция для выдачи правильного склонения. Оформил в виде хелпера.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
// ------------------------------------------------------------------------
/*
* Функция выбора нужной формы склонения существительного, стоящего перед числительным.
*
* Например: 1 символ, 2 символа, 5 символов
*
* Использование:
* declension(117, array('слово', 'слова', 'слов')); // выведет «117 слов»
* declension(52, 'слово слова слов', FALSE); // выведет «слова»
*
* @param integer $num Само число
* @param mixed $strings_125 Массив форм существительного или строка со словами, разделенными пробелами
* @param boolean $with_num Возвращать слово с числом (TRUE) или без (FALSE)
*/
if ( ! function_exists('declension'))
{
function declension($num, $strings_125, $with_num = TRUE)
{
if ( is_string($strings_125) ) { $strings_125 = explode(" ", $strings_125); }
$dec = abs($num) % 100;
$unt = $dec % 10;
if ($dec > 10 && $dec < 20) { $result = $strings_125[2]; }
elseif ($unt > 1 && $unt < 5) { $result = $strings_125[1]; }
elseif ($unt == 1) { $result = $strings_125[0]; }
else { $result = $strings_125[2]; }
return ($with_num ? $num . " " : "") . $result;
}
}
/* End of file declension_helper.php */
/* Location: ./application/helpers/declension_helper.php */
Затем внесем небольшие изменения в языковой файл form_validation_lang.php (предполагается, что у вас уже создан каталог с русскими языковыми файлами). Например
$lang['min_length'] = array("Поле «%s» должно содержать минимум %s.", "символ символа символов");
То есть вместо простой строки мы объявляем массив, первый элемент которого — шаблон строки, второй — строка (или массив) с формами слова.
Далее, необходимо переопределить метод _execute библиотеки валидации. Внесенный код отделен комментариями.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class MY_Form_validation extends CI_Form_validation {
/**
* Constructor
*
*/
function MY_Form_validation($rules = array())
{
parent::CI_Form_validation();
$this->CI =& get_instance();
// Validation rules can be stored in a config file.
$this->_config_rules = $rules;
// Automatically load the form helper
$this->CI->load->helper('form');
// Set the character encoding in MB.
if (function_exists('mb_internal_encoding'))
{
mb_internal_encoding($this->CI->config->item('charset'));
}
log_message('debug', "Form Validation Class Initialized");
}
// --------------------------------------------------------------------
/**
* Executes the Validation routines
*
* @access private
* @param array
* @param array
* @param mixed
* @param integer
* @return mixed
*/
function _execute($row, $rules, $postdata = NULL, $cycles = 0)
{
// If the $_POST data is an array we will run a recursive call
if (is_array($postdata))
{
foreach ($postdata as $key => $val)
{
$this->_execute($row, $rules, $val, $cycles);
$cycles++;
}
return;
}
// --------------------------------------------------------------------
// If the field is blank, but NOT required, no further tests are necessary
$callback = FALSE;
if ( ! in_array('required', $rules) AND is_null($postdata))
{
// Before we bail out, does the rule contain a callback?
if (preg_match("/(callback_\w+)/", implode(' ', $rules), $match))
{
$callback = TRUE;
$rules = (array('1' => $match[1]));
}
else
{
return;
}
}
// --------------------------------------------------------------------
// Isset Test. Typically this rule will only apply to checkboxes.
if (is_null($postdata) AND $callback == FALSE)
{
if (in_array('isset', $rules, TRUE) OR in_array('required', $rules))
{
// Set the message type
$type = (in_array('required', $rules)) ? 'required' : 'isset';
if ( ! isset($this->_error_messages[$type]))
{
if (FALSE === ($line = $this->CI->lang->line($type)))
{
$line = 'The field was not set';
}
}
else
{
$line = $this->_error_messages[$type];
}
// Build the error message
$message = sprintf($line, $this->_translate_fieldname($row['label']));
// Save the error message
$this->_field_data[$row['field']]['error'] = $message;
if ( ! isset($this->_error_array[$row['field']]))
{
$this->_error_array[$row['field']] = $message;
}
}
return;
}
// --------------------------------------------------------------------
// Cycle through each rule and run it
foreach ($rules As $rule)
{
$_in_array = FALSE;
// We set the $postdata variable with the current data in our master array so that
// each cycle of the loop is dealing with the processed data from the last cycle
if ($row['is_array'] == TRUE AND is_array($this->_field_data[$row['field']]['postdata']))
{
// We shouldn't need this safety, but just in case there isn't an array index
// associated with this cycle we'll bail out
if ( ! isset($this->_field_data[$row['field']]['postdata'][$cycles]))
{
continue;
}
$postdata = $this->_field_data[$row['field']]['postdata'][$cycles];
$_in_array = TRUE;
}
else
{
$postdata = $this->_field_data[$row['field']]['postdata'];
}
// --------------------------------------------------------------------
// Is the rule a callback?
$callback = FALSE;
if (substr($rule, 0, 9) == 'callback_')
{
$rule = substr($rule, 9);
$callback = TRUE;
}
// Strip the parameter (if exists) from the rule
// Rules can contain a parameter: max_length[5]
$param = FALSE;
if (preg_match("/(.*?)\[(.*?)\]/", $rule, $match))
{
$rule = $match[1];
$param = $match[2];
}
// Call the function that corresponds to the rule
if ($callback === TRUE)
{
if ( ! method_exists($this->CI, $rule))
{
continue;
}
// Run the function and grab the result
$result = $this->CI->$rule($postdata, $param);
// Re-assign the result to the master data array
if ($_in_array == TRUE)
{
$this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result;
}
else
{
$this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result;
}
// If the field isn't required and we just processed a callback we'll move on...
if ( ! in_array('required', $rules, TRUE) AND $result !== FALSE)
{
continue;
}
}
else
{
if ( ! method_exists($this, $rule))
{
// If our own wrapper function doesn't exist we see if a native PHP function does.
// Users can use any native PHP function call that has one param.
if (function_exists($rule))
{
$result = $rule($postdata);
if ($_in_array == TRUE)
{
$this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result;
}
else
{
$this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result;
}
}
continue;
}
$result = $this->$rule($postdata, $param);
if ($_in_array == TRUE)
{
$this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result;
}
else
{
$this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result;
}
}
// Did the rule test negatively? If so, grab the error.
if ($result === FALSE)
{
if ( ! isset($this->_error_messages[$rule]))
{
if (FALSE === ($line = $this->CI->lang->line($rule)))
{
$line = 'Unable to access an error message corresponding to your field name.';
}
// ----------------------------------------------
elseif ( is_array($line) && is_numeric($param) )
{
$this->CI->load->helper('declension');
$param = declension($param, $line[1]);
$line = $line[0];
}
// ----------------------------------------------
}
else
{
$line = $this->_error_messages[$rule];
}
// Is the parameter we are inserting into the error message the name
// of another field? If so we need to grab its "field label"
if (isset($this->_field_data[$param]) AND isset($this->_field_data[$param]['label']))
{
$param = $this->_field_data[$param]['label'];
}
// Build the error message
$message = sprintf($line, $this->_translate_fieldname($row['label']), $param);
// Save the error message
$this->_field_data[$row['field']]['error'] = $message;
if ( ! isset($this->_error_array[$row['field']]))
{
$this->_error_array[$row['field']] = $message;
}
return;
}
}
}
// --------------------------------------------------------------------
}
// END Form Validation Class
/* End of file MY_Form_validation.php */
/* Location: ./application/libraries/MY_Form_validation.php */
Мда, ради 3-х строк кода приходится копировать пару сотен.
Все, можно пользоваться.
Вообще, конечно же, делать нужно изначально иначе, менять подход в корне. Но это именно костыль к существующей структуре.