В прошлый раз мы составляли список хотелок и среди них было ЧПУ. О человекопонятных урлах я так же начинал писать чуть ранее и приводил пример своего видения .htaccess правила для mod_rewrite, сейчас повтору его с некоторой поправкой связанной с моим просвящением в области SEO.
RewriteEngine on
RewriteRule !^(p/|favicon\.ico|robots\.txt|sitemap\.xml|sitemap\.xml\.tgz) index.php [L]
Теперь мы будем перенаправлять на index.php всё, кроме запросов в папку p(публичную), и на файлы favicon.ico, robots.txt, sitemap.xml. Теперь мы стали более дружественными к поисковикам и брат seoшник не будет нас проклинать.
Покончив с нелюбимыми настройками сервака переходим к хотелкам по классу запроса.
- в адресе могут содержаться контроллер, экшин и дополнительные параметры
- дополнительные параметры можно получать по номеру позиции в адресе
- дополнительные параметры можно получать по имени name:value
- параметры могут принимать значения по умолчанию либо сообщать об отсутствии
- параметры необходимо приводить в требуемому типу
- определять AJAX запросы
- реализовать роутинг запросов (это отдельная задача, которую прикрутим позже)
В начале разберёмся с типизацией, для этого создатим простенький класс с константами, чтобы не забыть допустимые типы.
class Type { const T_ARRAY = 'array'; const T_BOOLEAN = 'boolean'; const T_INTEGER = 'integer'; const T_FLOAT = 'float'; const T_STRING = 'string'; const T_OBJECT = 'object'; static public function toArray($value) { return (array) $value; } static public function toBoolean($value) { return (boolean) $value; } static public function toInteger($value) { return (int) $value; } static public function toFloat($value) { return (float) str_replace(',', '.', $value); } static public function toString($value) { return (string) $value; } static public function toObject($value) { return (object) $value; } }
Как видим, здесь только константы и статические классы, которые приводят к нужному типу. Методы вызываются по правилу Type::to<имя_константы>(<переменная>), например, для Type::T_INTEGER = ‘integer’ вызывается метод Type::toInteger($value) , который возвращает переменную приведённую к целому.
Теперь создаём класс Request, он будет вызываться cпомощью паттерна одиночка, это не обязательно и является дурацкой привычкой, к тому же если перейти к компонентному стилю, то очень удобно использовать гремучую смесь одиночки и реестра. Ну и у него должны быть методы getController, getAction, get — для получения параметров из строки запроса, метода isAjax — для определения AJAX запросов.
class Request { private $_url; private $_part = array(); private static $_instance; protected $_controller; protected $_action; /** * @param string $url * @return Request */ public static function singleton($url = null) { if (!self::$_instance) { self::$_instance = new Request($url); } return self::$_instance; } protected function __construct($url = null) { if (!$url) { $url = $_SERVER['REQUEST_URI']; } $this->_url = urldecode($url); $this->_part = array(); foreach (explode('/', $this->_url) as $k => $v) { if (!empty($v)) { $v = explode(':', $v); if (!isset($v[1])) { $this->_part[] = $v[0]; } else { $this->_part[$v[0]] = implode(':', array_slice($v, 1)); } } } $this->_controller = $this->get(0, 'index'); if (!preg_match('~^[a-z]+[a-z0-9_-]*$~', $this->_controller)) { $this->_controller = 'error'; } $this->_action = $this->get(1, 'index'); if (!preg_match('~^[a-z]+[a-z0-9_-]*$~', $this->_action)) { $this->_action = 'error404'; } } /** * return value from request * get(0), * get(0, 'default', Type::T_STRING) * get('test', 0, Type::T_INTEGER) * @param string $key * @param mixed $default * @param string $type * @return mixed */ public function get($key, $default = null, $type = null) { if (isset($this->_part[$key])) { if ($type) { if (!is_array($type)) { return call_user_func_array(array('Type', 'to'.ucfirst($type)), array($this->_part[$key])); } else { return call_user_func_array(array('Type', 'to'.ucfirst($type[0])), array($this->_part[$key])+$type[1]); } } else { return $this->_part[$key]; } } else { if (is_object($default) && $default instanceof Exception) { throw $default; } else { return $default; } } } public function has($key) { return isset($this->_part[$key]); } public function getController() { return $this->_controller; } public function getAction() { return $this->_action; } public function isAjax() { if ($this->get('ajax') == 1) { return true; } elseif (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { return true; } else { return false; } } }
Как работать с классом… Немножко поясню по методу get, в него можно передавать 3 параметра: ключ для параметра из строки запроса, дефолтное значение, тип.
Ключ может быть 0 или 1, для контроллера и экшина, а дальше либо по позиции, либо по хэш значению. /controller/action/param_2/name:value/
Дефолтное значение — это значение, которое будет присвоено если в строке запроса нет такого ключа. Есть маленький финт с передачей вместо значения исключения унаследованного от Exception, например, при удалении статьи необходим параметр id, если его нет то нужно выдать 404 страницу с сообщением, что параметр отсутствует. делается это так: $request->get(‘id’, new Exception(‘Нет необходимого параметра id’), Type::T_INTEGER); Кроме сообщения об ошибке, здесь ещё приводится значения к типу integer.
Для AJAX запросов мы проверяем переменную ajax, которая может передаваться в урле и имеет наибольший вес, затем проверяем заголовки, которые посылаются при ajax запросах.
Больше ничего размусоливать не хочется, времени последнее время катастрофически не хватает. Лето как никак, нужно отдыхать, чего и Вам желаю. Если есть вопросы — Welcom.
Кстати вот, что подумал может если урл такой: http://site.dev/controller/action/key1:1/key2:/,
то может key2 == null правильнее было бы?
ps подумал так может и правильнее, но с классом Request не покатит.. get($val) возвращает по дефолту null.. поэтому не проверишь была ли переменная передана:) с другой стороны не помню, что бы были обращения такого плана: $requestObj->get(2)..
точно, добавил метод $request->has(‘key2’)
По-моему, лучше переложить парсинг и роутинг URI на класс Dispatcher (который переложит задачу роутинга на Router), а Request заставить заниматся своими делами — обрабатывать входные данные, парсить заголовки, предоставлять информацию UserAgent’a в человеческом виде и т. д. imho.
Извините за некропостинг.
согласен, здесь название не удачное выбрал
AmdY — можно показать на примере как разделить Ваш класс Request на отдельные классы.. я частично понимаю данный вопрос и бы хотелось посмотреть в примере.
Спасибо!
У вас опечатка в предложении:
Как видим, здесь только константы и статические классы…
методы?
P.S.: Надеюсь премодерация комментариев включена