Интересно посмотреть, как люди антропоморфически оценивают несколько концепций программирования. Жирные модели все милые и добрые, раздутые контроллеры — злые, а синглтоны чреваты неосязаемыми подводными камнями. Но критики не останавливаются на этом; некоторые в настоящее время ворчат о новых концепциях, которые недавно вошли в повседневную разработку PHP, говоря, что Front Controllers — это избыточное «переосмысление колеса», которое следует отбросить ipso facto. Период.
Время от времени обвинять в синглетах, и даже быть немного расстроенным из-за жировых контролеров, является, в некоторой степени, довольно предсказуемой и здоровой реакцией. Но что плохого в том, чтобы иметь отличный и работающий фронт-контроллер?
Есть довольно существенный аргумент против фронт-контролеров. Основная роль фронт-контроллера в веб-приложениях заключается в инкапсуляции типичных циклов запроса / маршрута / отправки / ответа в границах легко потребляемого API, что в точности и делает веб-сервер. На самом деле процесс кажется избыточным на первый взгляд, но в сочетании с реализацией MVC мы фактически пытаемся создать контроллер, который, в свою очередь, контролирует другие контроллеры.
Несмотря на очевидное дублирование ролей, реализация фронт-контроллера появляется как ответ на растущие сложности разработки современных веб-приложений. Туннелирование всех запросов через одну точку входа, безусловно, является эффективным способом реализации механизма на основе команд, который не только позволяет маршрутизировать и отправлять команды соответствующим обработчикам, но также предоставляет гибкую структуру, которую можно массировать и масштабировать без особых затрат. ,
Честно говоря, передние контроллеры — это легко приручаемые существа. В простейшем сценарии наивное сочетание переписывания URL-адресов и нескольких операторов switch — это все, что нам нужно для маршрутизации и отправки запросов, хотя при работе может потребоваться обратиться к более сложным и детализированным реализациям, особенно если мы хотим отменить маршрутизацию. и диспетчеризация процессов через мелкозернистые объекты, вооруженные хорошо разделенными обязанностями.
В этой статье, состоящей из двух частей, я подробно расскажу о нескольких простых подходах, которые могут показаться вам привлекательными, особенно если вы пытаетесь реализовать расширяемый фронт-контроллер с нуля, без чрезмерного потоотделения во время процесса или необходимости справляться с бремя раздутого каркаса.
Маршрутизация и диспетчеризация простым способом
На самом деле, существует так много изящных опций, которые можно использовать для построения функционального фронт-контроллера, но я начну с того, что позволю моей прагматичной стороне показать (да, у меня есть). Первой реализации фронт-контроллера, которую я рассмотрю, будет поручена маршрутизация / диспетчеризация URI, которые соответствуют следующему формату:
BasePath / controllername / ActionName / param1 / param2 /.../ paramN
Если вы когда-либо пользовались платформой, использующей понятие параметризованных контроллеров действий, вышеуказанный URI должен быть вам знаком. Это довольно вездесущий шаблон. Конечно, наиболее сложной задачей здесь является разработка гибкого механизма, способного анализировать рассматриваемые URI без особой суеты. Это может быть достигнуто любым творческим путем: простым процедурным кодом или обращением к объектно-ориентированному коду. Я инкапсулирую гайки и болты логики маршрутизации / диспетчеризации под оболочкой одного класса:
<?php namespace LibraryController; interface FrontControllerInterface { public function setController($controller); public function setAction($action); public function setParams(array $params); public function run(); }
<?php namespace LibraryController; class FrontController implements FrontControllerInterface { const DEFAULT_CONTROLLER = "IndexController"; const DEFAULT_ACTION = "index"; protected $controller = self::DEFAULT_CONTROLLER; protected $action = self::DEFAULT_ACTION; protected $params = array(); protected $basePath = "mybasepath/"; public function __construct(array $options = array()) { if (empty($options)) { $this->parseUri(); } else { if (isset($options["controller"])) { $this->setController($options["controller"]); } if (isset($options["action"])) { $this->setAction($options["action"]); } if (isset($options["params"])) { $this->setParams($options["params"]); } } } protected function parseUri() { $path = trim(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH), "/"); $path = preg_replace('/[^a-zA-Z0-9]//', "", $path); if (strpos($path, $this->basePath) === 0) { $path = substr($path, strlen($this->basePath)); } @list($controller, $action, $params) = explode("/", $path, 3); if (isset($controller)) { $this->setController($controller); } if (isset($action)) { $this->setAction($action); } if (isset($params)) { $this->setParams(explode("/", $params)); } } public function setController($controller) { $controller = ucfirst(strtolower($controller)) . "Controller"; if (!class_exists($controller)) { throw new InvalidArgumentException( "The action controller '$controller' has not been defined."); } $this->controller = $controller; return $this; } public function setAction($action) { $reflector = new ReflectionClass($this->controller); if (!$reflector->hasMethod($action)) { throw new InvalidArgumentException( "The controller action '$action' has been not defined."); } $this->action = $action; return $this; } public function setParams(array $params) { $this->params = $params; return $this; } public function run() { call_user_func_array(array(new $this->controller, $this->action), $this->params); } }
FrontController
класса FrontController
сводятся к FrontController
URI запроса или, в конечном счете, к сборке нового с нуля с помощью нескольких основных мутаторов. После выполнения этой задачи метод run()
аккуратно отправляет запрос соответствующему контроллеру действий вместе с предоставленными аргументами, если таковые имеются.
Учитывая минимальное API, использование класса является простым двухэтапным процессом. Во-первых, поместите в корень сети типичный файл .htaccess
, например, такой:
ПереписатьEngine на RewriteCond% {REQUEST_FILENAME}! -F RewriteCond% {REQUEST_FILENAME}! -D RewriteRule ^ (. *) $ /Index.php
Во-вторых, настройте следующий фрагмент кода как index.php
:
<?php use LibraryLoaderAutoloader, LibraryControllerFrontController; require_once __DIR__ . "/Library/Loader/Autoloader.php"; $autoloader = new Autoloader; $autoloader->register(); $frontController = new FrontController(); $frontController->run();
В этом случае контроллер просто анализирует URI запроса и передает его указанному контроллеру действий, что, кстати, является его поведением по умолчанию. Тем не менее, возможно явно указать URI, вызвав соответствующие установщики следующим образом:
<?php $frontController = new FrontController(array( "controller" => "test", "action" => "show", "params" => array(1) )); $frontController->run();
Фронтальный контроллер здесь довольно гибок, его легко настраивать как для внутренних разборов запросов, так и для маршрутизации / отправки пользовательских, поступающих непосредственно из клиентского кода. Более того, предыдущий пример в двух словах показывает, как вызвать метод show()
гипотетического класса TestController
и передать ему один числовой аргумент. Конечно, можно использовать разные URI по желанию и поиграть со способностями контроллера. Поэтому, если вам скучно и вы хотите повеселиться бесплатно, просто сделайте это.
Хотя я должен признать, что мой тщательно разработанный класс FrontController
имеет довольно ограниченную функциональность, он делает заявление сам по себе. Это демонстрирует, что создание настраиваемого фронт-контроллера на самом деле является простым процессом, который можно успешно приручить, не используя неясные и запутанные принципы программирования.
С другой стороны, плохая новость заключается в том, что у класса есть много обязанностей, за которыми нужно следить. Если вы настроены скептически, просто проверьте метод run()
. Конечно, его реализация является чистой и компактной, и ее можно даже приспособить для перехвата крюков до / после отправки. Но он делает несколько вещей одновременно и ведет себя как точка охвата всех маршрутов и отправлений. Желательно, чтобы фронт-контроллер анализировался в более детализированных классах, чьи обязанности сужены для выполнения отдельных задач.
Само собой разумеется, что для того, чтобы получить такой перегнанный фронт-контроллер и запустить его, как ожидалось, требуется путешествовать по дороге к довольно большому количеству классов и интерфейсов. Я не хочу делать этот выпуск чрезмерно длинным, поэтому буду подробно останавливаться на деталях всего процесса реализации в следующей статье. Таким образом, у вас будет время повернуть и согнуть мой образец фронт-контроллера, чтобы он соответствовал вкусу вашего тонкого вкуса.
Заключительные мысли
Фронтальные контроллеры смехотворно просты в реализации с нуля, независимо от того, использует ли этот подход процедурный код или объектно-ориентированный код. А из-за своей непринужденной природы довольно легко масштабировать наивный, примитивный фронт-контроллер и перекладывать через его плечи весь шебанг, необходимый для обработки ресурсов RESTful за кулисами.
Вполне возможно, что наиболее запутанным аспектом написания фронт-контроллера является решение неявной дилеммы, когда речь заходит о принятии решения о том, должны ли запросы статически или динамически маршрутизироваться и отправляться соответствующим обработчикам. Не существует формального принципа, который предписывает, чтобы вся логика маршрутизации / диспетчеризации была заключена в границах контроллера или разбита на отдельные модули, которые можно повторно использовать независимо.
Именно последняя является формой реализации, которую я буду обсуждать в течение следующей статьи. Так что следите за обновлениями!
Изображение через Fotolia