Это хороший способ сопоставить URI с классом / методом в PHP для MVC - PullRequest
6 голосов
/ 12 августа 2011

Я новичок в MVC, так что это моя первая попытка, и я уверен, что вы, ребята, можете помочь мне в этом, спасибо за любые советы или помощь!

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

Первый блок кода - это просто мой файл .htaccess, который направляет все запросы через мой файл index.php.

Второй блок кода - это мой массив «Маршрутов», который сообщит объекту «Маршрутизатор», какой класс и метод вызывать, а также любые идентификаторы или номера подкачки, если они существуют.

Третий блок кода - это класс маршрутизатора.

Четвертый блок просто запускает класс

Таким образом, класс маршрутизатора должен использовать регулярное выражение для сопоставления URI с маршрутом на карте маршрутов. Теоретически, это просто звучит как плохая производительность, если существует список из 50+ маршрутов, на которых должно выполняться регулярное выражение, если Я делаю это по-другому? Основная причина, по которой я использую регулярные выражения, заключается в сопоставлении номеров страниц и номеров идентификаторов, когда они существуют в маршруте.

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

1) Итак, главный вопрос: что-то не так выглядит?

2) Есть ли лучший способ обнаружить, что находится в URI, чем использовать регулярное выражение в массиве, как я, рассмотрим его на сайте с высоким трафиком?

3) Поскольку все это направляется через файл index.php с этим, как мне поступить с обработкой AJAX-запросов?

Извините, если это сбивает с толку, я немного запутался!


.htaccess файл

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$   index.php?uri=$1    [NC,L,QSA]

Массив карт ()

/**
 * Map URI to class/method and ID and Page numbers
 * Must be an array
 */
$uri_route_map = array( 
    //forums
    'forums/' => array(
        'controller' => 'forums',
        'method' => 'index',
        'id_number' => '',
        'page_number' => ''),

    'forums/viewforum/(?<id_number>\d+)' =>  array(
        'controller' => 'forums',
        'method' => 'viewforum',
        'id_number' => isset($id_number),
        'page_number' => ''),  

    'forums/viewthread/(?<id_number>\d+)' =>  array(
        'controller' => 'forums',
        'method' => 'viewthread',
        'id_number' => isset($id_number),
        'page_number' => ''),

    'forums/viewthread/(?<id_number>\d+)/page-(?<page_number>\d+)' =>  array(
        'controller' => 'forums',
        'method' => 'viewthread',
        'id_number' => isset($id_number),
        'page_number' => isset($page_number)),

    // user routes
    // account routes
    // blog routes 
    // mail routes
    // various other routes
);

Класс маршрутизатора, который читает и соответствует массиву Map выше

/**
 * Run URI against our Map array to get class/method/id-page numbers
 */
 class Router
{
    private $_controller = '';
    private $_method = '';
    public $page_number = '';
    public $id_number = '';

    public function __construct($uri, array $uri_route_map)
    {
        foreach ($uri_route_map as $rUri => $rRoute)
        {
            if (preg_match("#^{$rUri}$#Ui", $uri, $uri_digits))
            {
                //if page number and ID number in uri then set it locally
                $this->page_number = (isset($uri_digits['page_number']) ? $uri_digits['page_number'] : null);
                $this->id_number = (isset($uri_digits['id_number']) ? $uri_digits['id_number'] : null);
                $this->_controller = $rRoute['controller'];
                $this->_method = $rRoute['method'];

                // just for debug and testing while working on it / will be removed from final code
                echo '<hr> $page_number = ' . $this->page_number . '<br><br>';
                echo '<hr> $id_number = ' . $this->id_number . '<br><br>';
                echo '<hr> $controller = ' . $this->_controller . '<br><br>';
                echo '<hr> $method = ' . $this->_method . '<br><br>';
                break;
            }else{
                $this->page_number = '';
                $this->id_number = '';
                $this->_controller = '404';
                $this->_method = '404';
            }
        }
    }

    public function getController()
    {
        return $this->_controller;
    }

    public function getMethod()
    {
        return $this->_method;
    }

    public function getPageNumber()
    {
        return $this->page_number;
    }

    public function getIDNumber()
    {
        return $this->id_number;
    }

    /**
     * Call our class and method from values in the URI
     */
    public function dispatch()
    {
        if (file_exists('controller' . $this->_controller . '.php'))
        {
            include ('controller' . $this->_controller . '.php');
            $controllerName = 'Controller' . $this->_controller;
            $controller = new $controllerName($this->getIDNumber(),$this->getPageNumber());
            $method = $this->_method;
            if (method_exists($this->_controller, $this->_method))
            {
                return $controller->$method();
            } else {
                // method does not exist
            }
        } else {
            // Controller does not exist
        }
    }

}

Запустите его

/**
 * Testing the class
 */
$uri = isset($_GET['uri']) ? $_GET['uri'] : null;
$router = new Router($uri, $uri_route_map);
$router->dispatch();

?>

Ответы [ 6 ]

4 голосов
/ 12 августа 2011

1), 2) Я не думаю, что будет хорошей идеей поместить id_number и page_number в Router, потому что в будущем вы можете встретить много других параметров для url. Лучше просто используйте контроллер и метод и определите в контроллере, что делать с другими параметрами, или создайте другой класс Request, который имеет дело с информацией запроса.

3) Для ajax используйте url как ajax / module / action. И создайте контроллер ajax, который выполняет базовые функции безопасности ajax, такие как проверка на XSRF, а затем решает, какие контроллеры запускать и какие действия вызывать.

4 голосов
/ 12 августа 2011

1) Хорошо смотри мне.Код выглядит немного грязно.

2) Да, есть лучший способ.Вы делаете регулярное выражение, потому что вы хотите сопоставить части URL, которые вы не знаете.Почему бы не сделать $parts = explode("/", $uri) и посмотреть, сможете ли вы найти страницу, которую ищете?Вам нужно будет определить, сколько параметров вы ожидаете для каждой страницы, или вы не знаете, выбрать forums с параметрами array("viewform", 123) или forums/viewforum с параметрами array(123).

explode чувствуетзагружает лучше, чем регулярное выражение.Это также добавляет преимущество улучшенной обработки ошибок.Что если аргумент, переданный viewforum, не является числом?Конечно, вы можете сделать лучше, чем "404";)

3) Сделать отдельный обработчик AJAX.В любом случае Ajax скрыт от просмотра, поэтому вам не нужно беспокоиться о предоставлении семантических URL.

Пример:

function find_route($parts) {
    foreach ($uri_route_map as $route => $route_data) {
        $route_check = implode("/", array_slice($parts, 0, count($parts) - $route_data['num_arguments']));
        if ($route_check === $route) {
            return $route_data;
        }
    }
    throw new Exception("404?");
}

$uri = "forum/viewforum/522";

$parts = explode("/", $uri);
$route = find_route($parts);
$arguments = array_slice($parts, count($parts) - $route['num_arguments']);

$controller = $rRoute['controller'];
$method = $rRoute['method'];
$controller_instance = new $controller();
call_user_func_array(array($controller_instance, $method), $arguments);

(не проверено)

Плагины

Из-за $ uri_route_map вы не можете «динамически» регистрировать больше плагинов или страниц или «маршрутов».Я бы добавил функцию для добавления большего количества маршрутов динамически к Router.

. Кроме того, вы можете рассмотреть некоторую схему автообнаружения, которая, например, проверит папку plugins/ на наличие папок с файлом с именем«manifest.php», который при вызове может добавить дополнительные маршруты к маршрутизатору.

2 голосов
/ 22 августа 2011

1) Итак, главный вопрос: что-то не так выглядит?

Лично я вижу, что с ростом вашего сайта это становится все сложнее.Среда MVC, как меня учили, в значительной степени должна быть «Установи и забудь» - вы отделяете обработчик запросов (контроллер) от запросов к базе данных и бизнес-конец (модель) и от элементов отображения (представление).

[ Примечание: Вам могут понадобиться другие основные аспекты.Моя стандартная структура включает в себя некоторые основные элементы, которые переносят сеанс через различные части, а также обрабатывают фундаментальные аспекты работы сайта. Например, в то время как модели отвечают за правильные обращения к базе данных в соответствии с указаниями контроллера, есть ядрофункции в файле sql.class.php, которые предоставляют мне стандартизированный набор методов для выполнения этих вызовов и доставки или кэширования результатов по мере необходимости.]

Ваш метод отправки находится на правильном пути с этим - вы извлекаетеиз URI имя контроллера (форумы, профили и т. д.).Вам нужна карта Ури?Я чувствую, что вы создаете ненужную ситуацию, в которой вы должны обновлять эту карту каждый раз, а не просто создавать новый контроллер, когда вам нужны новые функции, и регистрировать его в базе данных.Я не говорю, что вы не правы сами по себе, я просто не чувствую, что сделал бы это таким образом.

2) Есть ли лучший способ обнаружить, что находится в URIчем использовать регулярное выражение в массиве, как я это делаю, рассмотрите его на сайте с большим трафиком?

Контролируйте результат (не каламбур, так как здесь работает контроллер).Рассмотрите этот подход и посмотрите, как он работает для вас:

Ваш файл index.php (он же «Главный контроллер») захватывает URI и разбивает значения вдоль «/» на биты.бит [0] - это идентификатор контроллера - здесь написано «Я хочу использовать контроллер с именем bit [0] => значение».Это делается следующим образом:

require_once( dirname( __FILE__ )."/controllers/".$bit[0]."controller.php" ); 

Лично, будучи немного уродливым, когда дело доходит до структур каталогов, я использую бит [0], чтобы идентифицировать каталог, в котором находится controller.php, так как яможет иметь субконтроллеры.

Именно этот файл контроллера я использую для разбора других битов.Для этого я буду использовать пример:

Предположим, что бит [0] содержит значение «форумы».Я мог бы передать, если он установлен, бит [1] в оператор switch.По умолчанию я всегда хочу составить список, но я могу специально указать его для «list», «view» или «post» в битах [1].Это скажет мне в классе контроллера, какой метод вызывать.Затем метод скажет мне вызвать соответствующую модель «форумы», если мне нужно будет выполнять запросы и кэшировать, например, список форумов.

Посторонние «биты» могут делать одно из двух: они могут бытьПередать в метод простые аргументы относительно того, какие данные запрашивать у модели, или бит [1] может быть достаточно сложным, чтобы гарантировать субконтроллер, и последующие биты будут переданы этому контроллеру для определения соответствующего действия, как былосделано с контроллером форумов.

Regex, будучи медленным, следует избегать, когда это возможно.Так как у нас может быть URI /forums/view/102305, мы можем предположить, что контроллер forums будет передавать 102305 методу, связанному с аргументом view (этот метод похож на private function displayPost( $id ), где $id равен 102305).Регулярное выражение не требуется, поскольку мы можем просто разбить значения по общему ожидаемому разделителю.

3) Поскольку все это направляется через файл index.php с этим, как мне поступить с обработкой запросов AJAX?

Не очень сложно.Если контроллер установлен, скажем, на AJAX, вы можете перестроить URL и получить прямой доступ к нему.Вы можете написать исключения в файле .htaccess (RewriteRule ^(AJAX)($|/) - [L]).Или (не идеальный, но хитрый способ) добавить ../ к вашему URI AJAX, чтобы вернуть URI обратно в root - он больше не пытается получить доступ к index.php, поэтому правило перезаписи не применяется.

Редактировать

Давайтепредположим, что мы используем URI /forums/id-1234/page-4 для вашего примера. Опять же, давайте предположим, как я упомянул выше, что forums относится к используемому контроллеру, а все остальные / разделяют аргументы (что я люблю называть «детализацией»). Итак, в нашем файле контроллера форума (назовем его forumcontroller.php, у нас может быть что-то вроде этого (очень упрощенного) конструктора:

// $registry is a class containing fundamental methods, and is meant to exemplify all 
// classes tied to the main controller "index.php". Keep in mind, I'm assuming we've 
// found the right controller by exploding the URI, and passed the remainder as bits
// to the constructor.
public function __construct( registry $registry ) {
  $this->registry = $registry;  //tying this controller to main controller.
  // For ease and clarity, we're assuming there's no case in which you wouldn't have
  // bits set. Error checking is easy.
  $bits = $this->registry->getURLBits;
  switch( $bits[0] ) {
    case 'view': $this->showForumEntry( $bits[1], (isset( $bits[2] ) ? $bits[2] : '' ); break;
    case 'edit': $this->editForumEntry( $bits[1] ); break;
    case 'post': $this->postForumEntry(); break;
    default: $this->listForumEntries(); break;
  }
}

private function showForumEntry( $thread, $offset ) {
  // Because you wanted to prepend id to the id element, we can use this for
  // cheekiness in the query if our DB is well designed.
  $data = explode('-', $thread);
  // Select all from forums where id = 1234
  $sql = "SELECT * FROM forums WHERE $data[0] = $data[1]";
  if( $offset != '' ) {
    $page = explode('-', $offset);
    $offset = $page[1] * 25; // Or whatever your max per page is. Make it dynamic.
    $max = $offset+25;
    $sql .= " LIMIT $offset, $max";
  }
  // You see where I'm going with this...
}

Суть в том, что вы контролируете то, что передается и как это обрабатывается. Управляйте URI, и вы сможете упростить их обработку.

Редактировать 2 Прочитав еще раз, я думаю, что вам помогут несколько концепций, с которыми вам следует ознакомиться:

Просмотрите шаблон «Фабрика» здесь (мой реестр $, в своей основе, представляет собой набор фабрик): http://php.net/manual/en/language.oop5.patterns.php

Хорошая графическая разбивка MVC: http://best -practice-software-engineering.ifs.tuwien.ac.at / модели / изображения / mvc3.jpg

Подробнее о фабричных методах: http://www.devshed.com/c/a/PHP/Design-Patterns-in-PHP-Factory-Method-and-Abstract-Factory/

Еще одно замечание, и это личное наблюдение после работы с Joomla, Drupal, Wordpress и различными корпоративными решениями CMS и BBS - разрабатывайте исключительно с учетом ваших потребностей. Когда вы начинаете пытаться стать «чем-то для всех», вы в конечном итоге получаете множество ненужных наворотов, которые загружаются с каждой страницей и используются 1 раз из 100. MVC - это шаблон проектирования, и использование его в качестве шаблона покажет вам избавиться от лишнего во всех аспектах, включая URI. Обработка /controller/arg1-Identifier/arg2-offset не требуется, и вы легко можете воспользоваться /controller/id/offset (например, /forums/1234/4). Если вы хотите сделать его более удобным для SEO, добавьте заголовок темы, а не тег, идентифицирующий идентификатор (например, /forums/1234-This-Is-A-Topic/4).

Теперь давайте также отметим очевидное, что касается моего редактирования выше - это контроллер, предназначенный исключительно для элемента форума. Каждый элемент вашего сайта (то есть форумы, галереи, профили и т. Д.) Должен иметь свой собственный контроллер. Зачем? Потому что каждый делает совершенно разные вещи на своих страницах. Так что воспользуйтесь этим - вам не нужно использовать карту URI, если вы понимаете, что направляете к контроллеру, а контроллер передает полномочия модели и любым вспомогательным контроллерам, которые могут понадобиться.

Я очень надеюсь, что это поможет.

2 голосов
/ 19 августа 2011

1) & 2) Я не скажу, это не правильно, но почему бы не использовать маршруты по умолчанию? Большую часть времени маршрут, как

контроллер / действие / param1 / param2

достаточно для большинства ваших страниц.

Вы, вероятно, могли бы сделать что-то подобное для определения маршрутов по умолчанию:

$this->controller = 'index';
$this->action = 'index';

private function getDefaultRoutes()
{
    $url = $_SERVER['REQUEST_URI'];
    $tabUrl = explode('/',$url);


    if(!empty($tabUrl))
    {
        $this->controller = array_shift($tabUrl);
        $this->action = array_shift($tabUrl);
        $this->params = $tabUrl;
    }
}

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

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

Также, пожалуйста, не говорите мне просто использовать фреймворк

Не забудьте скачать некоторые известные фреймворки и посмотреть их код. Это лучший способ учиться. Делая это, вы, вероятно, найдете много хороших практик и ответов.

1 голос
/ 23 августа 2011

Если я могу добавить пару пунктов:

  • Удалить id_number и page_number из роутера - просто передайте все, что было сопоставлено контроллеру, в конце концов, этозадание контроллера для обработки этих данных, а не маршрутизатор

  • Не передавайте $uri в конструктор, вместо этого передайте его dispatch().

  • Почему эти isset() -ы в $uri_route_map?Очевидно, они будут false, поскольку $uri_route_map определено до того, как будет создан экземпляр Router().

  • Рекомендовал бы добавить больше логики в процедуру сопоставления - в вашем текущем случае sitename/forumsничего не будет соответствовать, что приведет к 404 (без косой черты)

  • Вы также можете определить параметры по умолчанию в вашем $uri_route_map, а затем array_merge их с соответствующими параметрами.Так, например, если номер страницы не указан, page_number будет равно 1

  • . Если вас беспокоит производительность на веб-сайте с высоким трафиком, вы можете кэшировать маршруты.В конце концов, forums/viewforum/100 всегда будет указывать на один и тот же контроллер / метод.

И почему вы беспокоитесь об отправке запросов AJAX в ваш файл index.php?Что за проблема с этим?

1 голос
/ 21 августа 2011

1) Это работает? Если да, то да. Поскольку приведенный выше код содержит только массив, регулярное выражение и проверку для этого, я не думаю, что есть проблемы с вашим кодом. Пока это работает. Но если вы спросите: «Этот код является масштабируемым?» тогда ответ будет различным, и все это будет зависеть от ваших целей MVC Framework (например, является ли эта среда для общего использования, например: блог, или, в частности, для поставщика REST API. И так далее ...)

2) Да. Кохана, Zend, CI и другие популярные (и высоко оптимизированные) PHP-фреймворки используют это (массив + регулярное выражение на маршрутизаторе).

3) Я думаю, вы могли бы просто указать ему флаг в блоке / секции маршрута и сделать этот флаг доступным в качестве глобальной переменной. Таким образом, в вашем контроллере вы можете решить, какой ответ отправить для другого типа запроса (ajax / non-ajax), проверив этот флаг (например, вы можете предоставить $this->is_ajax как глобальный метод, доступный в области действия контроллера).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...