Класс запроса — Request

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

Покончив с нелюбимыми настройками сервака переходим к хотелкам по классу запроса.

  1. в адресе могут содержаться контроллер, экшин и дополнительные параметры
  2. дополнительные параметры можно получать по номеру позиции в адресе
  3. дополнительные параметры можно получать по имени name:value
  4. параметры могут принимать значения по умолчанию либо сообщать об отсутствии
  5. параметры необходимо приводить в требуемому типу
  6. определять AJAX запросы
  7. реализовать роутинг запросов (это отдельная задача, которую прикрутим позже)

В начале разберёмся с типизацией, для этого создатим простенький класс с константами, чтобы не забыть допустимые типы.

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.

Класс запроса — Request: 6 комментариев

  1. fluid

    Кстати вот, что подумал может если урл такой: http://site.dev/controller/action/key1:1/key2:/,
    то может key2 == null правильнее было бы?

    ps подумал так может и правильнее, но с классом Request не покатит.. get($val) возвращает по дефолту null.. поэтому не проверишь была ли переменная передана:) с другой стороны не помню, что бы были обращения такого плана: $requestObj->get(2)..

  2. Проходил мимо

    По-моему, лучше переложить парсинг и роутинг URI на класс Dispatcher (который переложит задачу роутинга на Router), а Request заставить заниматся своими делами — обрабатывать входные данные, парсить заголовки, предоставлять информацию UserAgent’a в человеческом виде и т. д. imho.

    Извините за некропостинг.

  3. Проходил мимо2

    AmdY — можно показать на примере как разделить Ваш класс Request на отдельные классы.. я частично понимаю данный вопрос и бы хотелось посмотреть в примере.
    Спасибо!

  4. Станислав Тамат

    У вас опечатка в предложении:
    Как видим, здесь только константы и статические классы…
    методы?

    P.S.: Надеюсь премодерация комментариев включена

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *