Request

Переносить классы c phpclasses я начал с Cache Manager, а это второй класс под названием Request. Его я люблю не меньше, в основном за его гибкость, он позволяет проверить любую переменную переданную ему на соответствие шаблону. Фактически класс есть оберткой для функции preg_match(), но позволяет не задумываться о параметрах.

Основная задача, которую решает класс — это фильтрация пользовательских данных, что есть неотъемлемой частью безопасности сайта. Например, можно проверять данные из GET запроса, перед тем как использовать их в SQL запросе. Данные можно проверять как из суперглобальных массивов ($_GET, $_POST, $_COOKIE, etc) и просто передать переменную, которую вы хотите проверить по шаблону. Класс предоставляет предустановленный набор таких шаблонов для самых частых проверок таких как цифровые (INT), буквенные (LIT), цифробуквенные (ALN) переменные, также можно валидировать email адреса (EML) и ссылки (URL), или использовать преобразования как например применения функции htmlspecialchars() (TXT) или вырезания специальных символов (SLU). Если вам не хватает готовых правил, вы всегда можете дописать и использовать свои или же передать в класс регулярное выражение вместо названия фильтра.


class Request{
	
	/**
	 * @author CTAPbIu_MABP
	 * @version 1.5
	 * @license GNU General Public License
	 *
	 */
	
	private $value  = NULL;					// переменная переменная	#ANY
	private $shield = FALSE;				// щит (выключен)		#bool
	private $maxlen = 0;					// 0 - неограничено		#integer
	private $mode = 'INT';					// фильтр для массивов	#string
	private $quotes = NULL;				// magic quotes gpc		#bool

	/**
	 * Конструктор заполняет основные свойства
	 *
	 * @param boolean $shield
	 * @param string $default
	 */
	public function __construct($shield=FALSE,$default='INT'){
		$this->quotes = (bool)get_magic_quotes_gpc();
		$this->shield = $shield;
		$this->mode = $default;
	}

	/**
	 * Основной метод класса, управляет процессом фильтрации
	 *
	 * @param mixed $name string or array of string
	 * @param string $type name or regexp
	 * @param mixed $source
	 * @param integer $maxlen
	 * @return mixed
	 */
	public function define($name, $type, $source='R', $maxlen=0){
		if (is_array($name)){
			foreach ($name as $single)
				$array[] = $this->define($single, $type, $source, $maxlen);
			return $array;
		}else{
			$this->maxlen = $maxlen;
			$_SOURCE =& $this->source($source, $name);
			
			if (!array_key_exists($name, $_SOURCE))
				return $this->$name = $this->shield ? $this->log($type, $name) : NULL;
			else {
				$this->type($type, $_SOURCE[$name]);
				if ($this->maxlen > 0 && !is_array($this->value)) 
					$this->value = substr($this->value, 0, $this->maxlen);
				return $this->$name = $this->value;
			}
		}
	}

	/**
	 * Переводит в глобальную область видимости все отфильтрованные переменные
	 *
	 */
	public function globalize(){
		$slice = array_keys(array_slice((array)$this, 5));
		$args = func_num_args() ? array_intersect(func_get_args(), $slice) : $slice;
		foreach ($args as $key)
			$GLOBALS[$key] = !isset($GLOBALS[$key]) ? $this->$key : $GLOBALS[$key];
	}
	
	/**
	 * Возвращает имена всех отфильтрованных переменных
	 *
	 * @return array
	 */
	public function getnames(){
		return array_keys(array_slice((array)$this, 5));
	}
	
	/**
	 * Получает все доступные переменные из указанного источника
	 *
	 * @param string|array $source
	 * @return unknown
	 */
	public function getall($source='R'){
		$_SOURCE =& $this->source($source);
		$names = array_keys($_SOURCE);
		foreach ($names as &$name) {
			$type = is_array($_SOURCE[$name]) ? 'ARR' : $this->mode ;
			$name = $this->define($name, $type, $source, 0);
		}
		return $names;
	}
	
	/**
	 * Устанавливает режим фильтрации по умолчанию 
	 *
	 * @param string $mode
	 */
	public function setmode($mode){
		$this->mode = $mode;
	}

	/**
	 * Устанавливает щит
	 *
	 * @param boolean $mode
	 */
	public function setshield($mode){

		$this->shield = $mode;
	}

	/**
	 * Уничтожает все отфильтрованные переменные
	 *
	 */
	public function undefine(){
		$slice = array_keys(array_slice((array)$this, 5));
		$args = func_num_args() ? array_intersect(func_get_args(), $slice) : $slice;
		foreach ($args as $name)
			unset($this->$name);
	}
	
	/**
	 * Устанавливает, какого именно типа переменная
	 *
	 * @param string $type
	 * @param mixed $value
	 */
	private function type($type, &$value){
		$value = $this->quotes && !is_array($value) ? stripcslashes($value) : $value;
		
		if(is_array($value) && $type!='ARR')
			$this->log($type,$value);
		else 
			$value = $this->quotes ? stripcslashes($value) : $value;
					
		if(method_exists($this,"_is_".$type))
			$this->{"_is_".$type}($value);
		else
			$this->value = $this->test($type,$value);
	}

	/**
	 * Выбирает источник переменной
	 *
	 * @param string $source
	 * @param string $name
	 * @return array
	 */
	private function & source($source,$name){
		$_source = is_string($source) ? strtoupper($source) : 'default';
		switch ($_source){
			case 'R': case 'REQUEST':	return $_REQUEST; break;
			case 'P': case 'POST':		return $_POST; break;
			case 'G': case 'GET':		return $_GET; break;
			case 'C': case 'COOKIE':	return $_COOKIE; break;
			case 'F': case 'FILES':		return $_FILES; break;
			case 'ENV':		return $_ENV; break;
			case 'SERVER':	return $_SERVER; break;
			case 'SESSION':	return $_SESSION; break;
			case 'GLOGALS':	return $GLOBALS; break;
			default: $a = array($name=>$source); return $a; break;
		}
	}

	/**
	 * Бросает исключение, если переменная оказалась не предполагаемого типа
	 *
	 * @param string $regexp
	 * @param string $value
	 * @param mixed $return
	 * @return mixed
	 */
	private function log($regexp, $value, $return=null){
		if ($this->shield) 
			throw new Exception("Request: unexpected type of variable '{$value}' expected '{$regexp}'");
		return $return;
	}
	
	/**
	 * Проверяет строку на соответствие регулярному выражению
	 *
	 * @param string $regexp
	 * @param string $value
	 * @param mixed $return
	 * @return mixed
	 */
	private function test($regexp, $value, $return=null){
		return preg_match($regexp, $value) ? $value : $this->log($regexp, $value, $return);
	}
	
	/* числовые */
	private function _is_INT($value){
		$this->value = $this->test('/^[0-9]+$/', $value);
	}
	private function _is_NUM($value){
		$this->value = $this->test('/^-?([0-9]+)(.[0-9]+)?$/', $value);
	}
	
	/* строчные */
	protected function _is_HEX($value){
		$this->value = $this->test('/^[a-f0-9]+$/i', $value);
	}
	private function _is_LIT($value){
		$this->value = $this->test('/^[a-z]+$/i', $value);
	}
	private function _is_ALP($value){
		$this->value = $this->test('/^[a-z-_]+$/i', $value);
	}
	private function _is_ALN($value){
		$this->value = $this->test('/^[a-z0-9-_]+$/i', $value);
	}
	protected function _is_RUS($value){
		$this->value = $this->test('/^[а-яё0-9-_]+$/i', $value);
	}
	protected function _is_UKR($value){
		$this->value = $this->test('/^[а-яєїіґ-_]+$/i', $value);
	}
	
	/* шаблонные */
	private function _is_EML($value){
		$this->value = $this->test('/^[_a-z0-9-.]+@[a-z0-9-]+(.[a-z0-9-]{2,})+$/', $value);
	}
	private function _is_URL($value){
		$this->value = $this->test('/(ht|f)tp(s)?://(www.)?([a-z][.a-z0-9_-]+)(:[0-9]+)?/([^?]+)???([^#]+)?(#.*)?$/', $value);
	}
	
	/* булевые */
	private function _is_BOL($value){
		$this->value = ($value===TRUE || $value==1 || in_array($value, array('on','ON','true','TRUE','yes','YES'))) ? TRUE : $this->log($value, 'BOL', false);
	}

	/* заменяющие */
	private function _is_HTM($value){
		$this->value = $value;
	}
	
	private function _is_SLU($value){
		$this->value = str_replace(array("\",""","'","(",")","%","<",">","{","}","/","&","+"), '', $value);
	}
	private function _is_TXT($value){
		$this->value = htmlspecialchars($value, ENT_QUOTES);
	}
	
	/* массив */
	private function _is_ARR($value){
		if (is_array($value)){
			$tmp = $value;
			foreach ($tmp AS $k => &$v){
				$value = $this->quotes && !is_array($v) ? stripcslashes($v) : $v;
				if (is_array($value)) 
					$this->_is_ARR($value);
				else 
					$this->{"_is_".$this->mode}($value);
				$tmp[$k] = $this->value;
			}
			$this->value = $tmp;
		}else
			$this->value = $this->log($value,'ARR');
	}
	
	public function __destruct(){
		
	}
}

Я думаю, я уже достаточно рассказал, теперь надо бы показать. Поскольку Я не могу тут реально использовать POST и GET запросы, поэтому я буду имитировать их в коде при помощи обычного массива. Итак, задача первая: получение данных из суперглобальных массивов по шаблону


// создаем новый экземпляр класса
$gpc = new Request();

/*
имитируем $_GET запрос
при этом url должен выглядеть вот так
index.php?string=x_y_z&int=100&num=0.10&hex=FACE8D
*/
$_GET = array ( 
	'string' => 'x_y_z',
	'int' => '100', 
	'num' => '0.10', 
	'hex' => 'FACE8D', 
);

/* получаем данные */
// строчный: алфавитные символы от a до z , тире - и подчерк _
$gpc->define('string','ALP','G');
// целые числа: от 0 до 9
$gpc->define('int','INT','G');
// числовые: содержащие в себе точку
$gpc->define('num','NUM','G');
// 16тиричные цыфры: от 1 до 0 и от a до f
$gpc->define('hex','HEX','G');

/*
Теперь попробуем получить сразу несколько переменных из куков
для этого имитируем массив $_COOKIE
var1=first&var2=second&var3=third&var4=fourth
*/

$_COOKIE = array ( 
	'var1' => 'first', 
	'var2' => 'second', 
	'var3' => 'third', 
	'var4' => 'fourth', 
);

// все полученные переменные будут содержать только латинские буквы от a до z
$gpc->define(array('var1','var2','var3','var4'),'LIT','C');

/*
И последний пример из этой серии - получение массива из массива $_POST
array[1]=on&array[2]=off&array[3]=no&array[4]=yes
*/

$_POST = array( 
	'array' => array ( 
		1 => 'on', 
		2 => 'off', 
		3 => 'no',
		4 => 'yes'
	)
);

// все переменные у нас будут булевы, поэтому мы выставляем фильтрование по умолчанию булевым
$gpc->setmode('BOL');
// дальше все как обычно, только указываем, что хотим получить массив
$gpc->define('array','ARR','P');

Надо заметить что для суперглобальных массивов таких как $_POST, $_GET, $_COOKIE, $_FILES и $_REQUEST можно указывать только первую букву (G,P,C,F,R) имена остальных $_ENV, $_SERVER, $_SESSION и $GLOBALS надо писать полностью. Но если вам не нужно использование этих массивов то вы вполне можете передать вместо них переменную, которую вы хотите провалидировать


$gpc->define('eml','EML','CTAPbIuMABP@gmail.com');
$gpc->define('url','URL','http://mabp.kiev.ua/');

Так же если вас не устраивает набор стандартных правил вы можете написать вместо названия правила регулярное выражение


// правило валидации пользователей на gmail.com
$gpc->define('email','/^[a-z0-9.]+@gmail.com$/i','CTAPbIuMABP@gmail.com');

Дополнительные функции. При помощи этих функция вы можете сэкономить код и время, но если честно я ими никогда не пользовался. Первая функция — getall пытается получить все доступные переменные из указанного суперглобального массива (и переданной переменной) в соответствии правилу фильтрации по умолчанию. Вторая функция — globalize переводит все отфильтрованные переменные в глобальную область видимости, а точнее делает их частью массива $GLOBALS. Следующая функция — getnames возвращает массив имен всех провалидированных переменных. И, наконец, последняя функция undefine убирает из объекта провалидированные переменные все или только те, что переданы в качестве параметров.


// перевели в глобальную область видимости 4 переменные
$gpc->globalize('string','int','num','hex');

// и удалили их из контекста объекта
$gpc->undefine('string','int','num','hex');

// получили имена всех оставшихся переменных
$names = $gpc->getnames();

// применение getall возможно, если заранее известно что, например, через массив $_GET придут только числовые значения
$_GET = array ( 
	'one' => 1,
	'two' => 2, 
	'three' => 3, 
	'four' => 4, 
);
$gpc->setmode('INT');
$gpc->getall('G');

Использование щита. Если вы хотите чтобы когда переменная не проходит валидацию бросалось исключение то включите щит ($gpc->setshield(true);) это бывает полезно при дебаге когда нужно видеть какие значения приходят от того или иного запроса.