В прошлый раз мы составляли список хотелок и среди них было ЧПУ. О человекопонятных урлах я так же начинал писать чуть ранее и приводил пример своего видения .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.
Извините за некропостинг.
согласен, здесь название не удачное выбрал