admin管理员组

文章数量:1579086

控制器模式

It’s interesting to see how people have passed judgement on several software programming concepts in an anthropomorphic fashion. Fat models are all sweet and good, bloated controllers are evil, and singletons are fraught with untameable pitfalls. But the critics don’t stop there; some are now grumbling about newer concepts that have recently made inroads in day-to-day PHP development, saying Front Controllers are a redundant “reinvention of the wheel” which should be discarded ipso facto. Period.

有趣的是,人们如何以拟人化的方式对几种软件编程概念做出判断。 胖模型都是好人,善良,controller肿的控制者是邪恶的,单身人士充满了无法克服的陷阱。 但是批评家们并不止步于此。 一些人现在抱怨最近在PHP开发中日新月异的概念,并说前端控制器是多余的“重塑方向”,应立即弃用。 期。

Pitching blame against singletons from time to time, and even being a bit cranky about fat controllers is, to some extent, a fairly predictable and healthy reaction. But what’s wrong with having a nifty front controller up and running?

在某种程度上,不时对单身人士大加指责,甚至对脂肪控制者有些怪异,这在一定程度上是可以预见和健康的React。 但是,安装和运行一个漂亮的前端控制器有什么问题?

There’s a pretty substantial argument against front controllers. The primary role of a front controller in web-based applications is to encapsulate the typical request/route/dispatch/response cycles inside the boundaries of an easily-consumable API, which is exactly what the web server does. Indeed the process seems redundant at first blush, but when coupled to an MVC implementation, we’re actually trying to build up a controller that in turn controls other controllers.

对于前端控制器,有相当大的争议。 在基于Web的应用程序中,前端控制器的主要作用是将典型的请求/路由/调度/响应周期封装在易于使用的API的边界内,而这正是Web服务器所做的。 确实,乍看之下,该过程似乎是多余的,但是当与MVC实现耦合时,我们实际上是在尝试构建一个控制器,该控制器进而控制其他控制器。

Despite the apparent duplication of roles, the implementation of a front controller emerges as a response to the growing complexities of modern web application development. Tunneling all requests through a single entry point is certainly an efficient way to implement a command-based mechanism, which not only allows you to route and dispatch commands to appropriate handlers, but also exposes a flexible structure that can be massaged and scaled without much burden.

尽管角色明显重复,但前端控制器的实现还是作为对现代Web应用程序开发日趋复杂的回应。 通过单个入口点隧道传输所有请求无疑是实现基于命令的机制的一种有效方法,该机制不仅使您可以将命令路由和分派到适当的处理程序,而且还提供了一种灵活的结构,可以在无需太多负担的情况下进行调整和扩展。

Frankly speaking, front controllers are easily-tameable creatures. In the simplest scenario, a naive combination of URL rewriting along with a few switch statements is all we need to route and dispatch requests, though in production it might be necessary to appeal to more complex and granular implementations, especially if we want to distill routing and dispatching processes through finer-grained objects armed with well-segregated responsibilities.

坦白说,前线指挥官是易于驯服的生物。 在最简单的情况下, URL重写和一些switch语句的天真组合是我们路由和分派请求的全部,尽管在生产中可能有必要吸引更复杂,更细化的实现,尤其是如果我们希望提取路由以及通过职责分明的细粒度对象进行调度过程。

In this two-part article I’ll be exploring in depth a couple of straightforward approaches that you might find appealing, especially if you’re trying to implement an expandable front controller from scratch without sweating excessively during the process or having to cope with the burdens of a bloated framework.

在这个由两部分组成的文章中,我将深入探讨一些可能会吸引您的简单方法,特别是如果您尝试从头开始实现可扩展的前端控制器而在过程中不会过多地出汗或必须应对framework肿的框架的负担。

简单的路由和调度 (Routing and Dispatching in a Straightforward Way)

In reality, there are so many nifty options that can be used for building a functional front controller, but I’ll start by letting my pragmatic side show (yes, I have one). The first front controller implementation that I’ll go through will be charged with routing/dispatching URIs that conform to the following format:

实际上,有很多漂亮的选项可用于构建功能性的前端控制器,但我将从让我务实的一面开始(是的,我有一个)开始。 我将经历的第一个前端控制器实现将负责遵循以下格式的路由/调度URI:

basepath/controllername/actionname/param1/param2/.../paramN

If you’ve ever laid your hands on a framework that uses the notion of parameterized action controllers, the above URI should familiar to you. It is a fairly ubiquitous pattern. Of course, the most challenging task here is designing a flexible mechanism capable of parsing the URIs in question without much fuss. This can be achieved in all sort of creative ways, either by plain procedural code or appealing to object-oriented code. I’ll be encapsulating the nuts and bolts of the routing/dispatching logic beneath the shell of a single class:

如果您曾经接触过使用参数化动作控制器概念的框架,那么上面的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);
    }
}

The FrontController class’ duties boil down to parsing the request URI, or eventually assembling a brand new one from scratch through a few basic mutators. Once this task has been carried out, the run() method neatly dispatches the request to the appropriate action controller, along with the supplied arguments, if any.

FrontController类的职责归结为解析请求URI,或者最终从头开始通过几个基本的mutator组装一个全新的。 完成此任务后, run()方法会将请求以及提供的参数(如果有run()整齐地分派给适当的动作控制器。

Given its minimal API, consuming the class is a simple two-step process. First, drop into the web root a typical .htaccess file, like this one:

鉴于其最小的API,使用类是一个简单的两步过程。 首先,将典型的.htaccess文件放入网络根目录,如下所示:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.php

Second, set up the following code snippet as 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();

In this case, the controller simply parses the request URI and feeds it to the given action controller, which is its default behavior by the way. It’s feasible, though, to explicitly provide a URI by calling the corresponding setters, as follows:

在这种情况下,控制器仅解析请求URI并将其提供给给定的动作控制器,这是其默认行为。 不过,通过调用相应的setter显式提供URI是可行的,如下所示:

<?php
$frontController = new FrontController(array(
    "controller" => "test", 
    "action"     => "show", 
    "params"     => array(1)
));

$frontController->run();

The front controller here is pretty malleable, easily configurable either for internally parsing requests or for routing/dispatching custom ones supplied directly from client code. Moreover, the previous example shows in a nutshell how to call the show() method of an hypothetical TestController class and pass around a single numeric argument to it. Of course, it’s possible to use different URIs at will and play around with the controller’s abilities. Therefore, if you’re bored and want to have some fun for free, just go ahead and do so.

此处的前端控制器具有很好的延展性,可以轻松配置以用于内部解析请求或路由/调度直接从客户端代码提供的自定义请求。 此外,前面的示例简述了如何调用假设的TestController类的show()方法,并将单个数字参数传递给它。 当然,可以随意使用不同的URI并发挥控制器的功能。 因此,如果您无聊并想免费享受一些乐趣,那就继续吧。

Though I have to admit that my carefully-crafted FrontController class has pretty limited functionality, it makes a statement on its own. It demonstrates that building up a customizable front controller is in fact a straightforward process that can be tamed successfully without having to use obscure and tangled programming principles.

尽管我必须承认精心设计的FrontController类的功能非常有限,但它只能自己声明。 它表明建立一个可定制的前端控制器实际上是一个简单的过程,可以成功地驯服,而不必使用晦涩而复杂的编程原理。

On the flip side, the bad news is that the class has way to many responsibilities to watch over. If you’re skeptical, just check out its run() method. Certainly its implementation is clean and compact, and can be even accommodated to intercept pre/post dispatch hooks. But it does multiple things at the same time and behaves pretty much as a catch-all point for routes and dispatches. It’s preferable to have a front controller dissected in more granular classes, whose responsibilities are narrowed to perform discrete tasks.

另一方面,坏消息是,班级有许多责任要注意。 如果您对此表示怀疑,请查看其run()方法。 当然,它的实现是干净紧凑的,甚至可以用来拦截前/后调度钩子。 但是它可以同时执行多项操作,并且在路由和调度方面表现得非常好。 最好将前端控制器分解为更精细的类,将其职责缩小以执行离散任务。

Needless to say that getting such a distilled front controller up and running as expected requires traveling down the road to a fairly prolific number of classes and interfaces. I’m reluctant to make this installment excessively lengthy, so I’ll be covering in depth the details of the whole implementation process in the next article. This way you can have some time to twist and bend my sample front controller and make it suit the taste of your delicate palate.

毋庸置疑,要使这种精简的前端控制器按预期方式启动和运行,就需要走很多路的类和接口。 我不愿意将这一部分过长,因此在下一篇文章中我将深入介绍整个实现过程的细节。 这样,您就可以花些时间扭曲和弯曲我的样品前控制器,使其适合您细腻的口感。

总结思想 (Closing Thoughts)

Front controllers are ridiculously simple to implement from scratch, regardless if the approach makes use of procedural code or object-oriented code. And because of its easy-going nature, it’s fairly easy to scale up a naïve, primitive front controller and pitch over its shoulders the whole shebang required for handling RESTful resources behind the scenes.

前端控制器非常容易从头开始实现,无论该方法是使用过程代码还是面向对象的代码。 而且由于其随和的性质,很容易扩展一个幼稚,原始的前控制器并在其肩膀上俯仰,以处理幕后的RESTful资源。

Quite possibly, the most tangled aspect of writing a front controller is solving the implicit dilemma when it comes to deciding if the requests must be either statically or dynamically routed and dispatched to the appropriate handlers. There’s no formal principle that prescribes all the routing/dispatching logic should be encapsulated within the controller’s boundaries or broken down into standalone modules that can be reused independently.

很有可能,编写前端控制器的最复杂的方面是解决隐式难题,这是在确定是否必须静态或动态路由请求并将其分派到适当的处理程序方面。 没有正式的原则规定所有路由/调度逻辑都应封装在控制器的边界内或分解为可以独立重用的独立模块。

Precisely, the latter is the form of implementation that I’ll be discussing over the course of the next article. So, stay tuned!

准确地说,后者是我将在下一篇文章中讨论的实现形式。 所以,请继续关注!

Image via Fotolia

图片来自Fotolia

翻译自: https://www.sitepoint/front-controller-pattern-1/

控制器模式

本文标签: 控制器模式简介