Является ли это хорошим способом сопоставления URI с классом / методом в PHP для MVC

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

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

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

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

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

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

Таким образом, класс маршрутизатора должен использовать регулярное выражение для соответствия 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 ); 

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

 /** * 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(); ?> 

1) Смотри хорошо. Однако код выглядит немного грязным.

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

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

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

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) Ваш маршрутизатор, вероятно, зависит от вашего индекса, без индекса no root, поэтому, к сожалению, вы, вероятно, не сможете его избежать. Поэтому очень важно избегать дорогостоящих действий в вашем индексе. Как правило, не запускайте соединение с базой данных в индексе, если вам не нужны все ваши страницы.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Просмотрите шаблон «Factory» здесь (My $ registry – это, в самом сердце, набор фабрик): http://php.net/manual/en/language.oop5.patterns.php

Хорошая разбивка MVC на графике: http://img.ruphp.com/php/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, если вы понимаете, что направляетесь к контроллеру, а контроллер делегирует ответственность модели и любым дополнительным контроллерам, которые могут понадобиться.

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

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

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

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

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

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

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

  • Почему эти isset() -s в $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? В чем проблема?