Склонение существительных в текстах ошибок библиотеки 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-х строк кода приходится копировать пару сотен.

Все, можно пользоваться.

Вообще, конечно же, делать нужно изначально иначе, менять подход в корне. Но это именно костыль к существующей структуре.

Личные инструменты