【问题标题】:How does MVC routing work?MVC 路由是如何工作的?
【发布时间】:2012-09-07 22:52:11
【问题描述】:

所以我开始更深入地研究 MVC(真正的 MVC,而不是框架 MVC),并且我正在尝试开发一个小型框架。我通过阅读 Symphony 和 Zend 等其他框架来工作,了解它们是如何工作的,并尝试自己实现它。

我卡住的地方是 URL 路由系统:

<?php
namespace Application\Common;

class RouteBuilder {

    public function create($name, $parameters) {
        $route           = new Route($name);
        $route->resource = array_keys($parameters)[0];
        $route->defaults = $parameters["defaults"];
        $notation        = $parameters["notation"];
        $notation = preg_replace("/\[(.*)\]/", "(:?$1)?", $notation);
        foreach ($parameters["conditions"] as $param => $condition) {
            $notation = \str_replace($param, $condition, $notation);
        }

        $notation = preg_replace("/:([a-z]+)/i", "(?P<$1>[^/.,;?\n]+)", $notation);

        //@TODO: Continue pattern replacement!!
    }
}
/* How a single entry looks like
 * "main": {
    "notation": "/:action",
    "defaults": {
        "resource"  :   "Authentication",
    },
    "conditions":   {
        ":action"   :   "(login)|(register)"
    }
},

 */

我只是无法正确地缠绕它。这里的应用程序工作流程是什么?

模式生成了,大概是Route对象要保存在Request对象下什么的,然后呢?它是如何工作的?

P.S. 在这里寻找一个真实的、解释清楚的答案。我真的很想了解这个主题。如果有人花时间写一个真正详尽的答案,我将不胜感激。

【问题讨论】:

  • 看看我的博客:www.ganymedes.be
  • @JvdBerg:我看不到任何关于路由本身的信息,你有更具体的链接吗?还有一个提示,在链接前加上 http:// 以便解析它们,这样您就可以获得不错的 SEO 奖励:P
  • 为什么不看看现有的框架,例如 Kohana 或 Fuel?
  • 我从我的框架中发布了路由器类。希望对您有所帮助。
  • @MrtiBriedis:我有,请阅读问题。我还是没明白(如果我没有用尽所有其他方法,我不会在这里问。

标签: php model-view-controller routes


【解决方案1】:

一个 MVC Router 类(它是一个更广泛的前端控制器的一部分)分解一个 HTTP 请求的 URL——特别是路径组件(可能还有查询字符串)。

Router 尝试将路径组件的第一个或两个部分匹配到相应的路由组合(Controller/Action [method],或者只是一个Controller执行默认操作(方法)。

动作或命令只是特定Controller的方法。

通常有一个abstract Controller 和Controller 的许多子级,每个网页一个(一般来说)。

有些人可能会说Router 还会将参数传递给所需的Controller 方法(如果 URL 中存在)。

注意:面向对象的编程纯粹主义者,遵循单一职责原则,可能会争辩说 路由 URL 的组件和 调度 @ 987654333@ 类是两个独立的职责。在这种情况下,Dispatcherclass 实际上会实例化Controller,并将从客户端 HTTP 请求派生的任何参数传递给它的方法之一。

示例 1:Controller,但没有操作或参数。

http://localhost/contact 在 GET 请求中,这可能会显示一个表单。

控制器 = 合同

操作 = 默认(通常是index() 方法)

=======================

示例 2:Controller 和操作,但没有参数。

http://localhost/contact/send 在 POST 请求中,这可能会启动服务器端验证并尝试发送消息。

控制器 = 合同

动作 = 发送

=======================

示例 3:Controller、动作和参数。

http://localhost/contact/send/sync 在 POST 请求中,这可能会启动服务器端验证并尝试发送消息。但是,在这种情况下,JavaScript 可能没有激活。因此,为了支持优雅降级,您可以告诉ContactController 使用支持屏幕重绘并以Content-Type: text/html 的HTTP 标头而不是Content-Type: application/json 响应的View。 sync 将作为参数传递给ContactConroller::send()。请注意,我的 sync 示例完全是随意的,而且是虚构的,但我认为它符合要求!

控制器 = 合同

动作 = 发送

参数 = [sync] // 是的,在数组中传递参数!

Router 类实例化所请求的具体子 Controller,从 控制器实例 调用所请求的 方法,并将其 传递给控制器​​方法参数(如果有的话)。

1) 您的Router 类应首先检查是否有一个具体的Controller 可以实例化(使用URL 中的名称,加上单词“Controller” )。如果找到控制器,test for the presence of the requested method(操作)。

2) 如果Router 在运行时无法找到并加载必要的PHP(建议使用autoloader)来实例化一个具体的Controller 子级,那么它应该检查一个数组(通常在另一个类名Route 中找到)查看请求的URL matches,使用正则表达式,包含其中的任何元素。下面是Route 类的基本骨架。

注意:.*? = 零个或多个任意字符,非捕获。

class Route
{
    private $routes = [
        ['url' => 'nieuws/economie/.*?', // regular expression.
         'controller' => 'news',
         'action' => 'economie'],
        ['url' => 'weerbericht/locatie/.*?', // regular expression.
         'controller' => 'weather',
         'action' => 'location']
    ];

    public function __contstruct()
    {

    }

    public function getRoutes()
    {
        return $this->routes;
    }
}

为什么要使用正则表达式?对于 URL 中第二个正斜杠之后的数据,不太可能完成可靠的匹配。

/controller/method/param1/param2/...,其中 param[x] 可以是任何东西!

警告:当目标数据包含模式分隔符(在这种情况下,正斜杠“/”)时,最好更改默认的正则表达式模式分隔符 ('/')。几乎所有非有效的 URL 字符将是一个不错的选择。

Router 类的方法将遍历 Route::routes 数组,以查看目标 URL 和与第二级关联的 string 之间是否存在正则表达式匹配值 url 索引。如果找到匹配项,Router 就会知道要实例化哪个具体的 Controller 以及要调用的后续方法。参数将根据需要传递给方法。

始终警惕边缘情况,例如代表以下内容的 URL。

`/`   // Should take you to the home page / HomeController by default
`''`  // Should take you to the home page / HomeController by default
`/gibberish&^&*^&*%#&(*$%&*#`   // Reject

【讨论】:

  • 如果你的版本是一个简化的例子,我看到的方法(即我的例子)比你的有什么优势?另外,如果找不到匹配项怎么办?
  • @MadaraUchiha 我和你的区别在于,你可以指定符号的外观。我看到您可以为操作方法添加某些条件。还有一些其他的事情。因此,换句话说,您的示例更具可配置性。 --- 我不确定你从哪里得到代码(Symphony?),所以我真的无法说出所有选项的真正含义。但总体思路与我的示例完全相同。此外,如果未找到匹配项,则应显示 404 页面 (404Controller)。因为请求的页面根本不存在。
  • 嗯,不应该默认为“resource/action/params”吗?
  • @MadaraUchiha 通常在其他框架中,default 选项是当 URI 不包含数据时应该调用的控制器/动作。否则,当 URI 包含数据,但未在 RouteBuilder 中找到时,您真的应该抛出 404 错误。我真的不会推荐任何其他方式。
  • 关于这件事的某些事情是不准确的,或者我对某些事情感到困惑:在没有 mod 的干净 apache 服务器的测试用例中,重写 localhost/news 之类的请求不会重新路由到 localhost/index.php? route=news 但导致 404 错误?
【解决方案2】:

路由器类,来自我的框架。代码讲述了这个故事:

class Router
{
  const default_action = 'index';
  const default_controller = 'index';

  protected $request = array();

  public function __construct( $url )
  {
    $this->SetRoute( $url ? $url : self::default_controller );
  }

  /*
  *  The magic gets transforms $router->action into $router->GetAction();
  */
  public function __get( $name )
  {
    if( method_exists( $this, 'Get' . $name ))
      return $this->{'Get' . $name}();
    else
      return null;
  }

  public function SetRoute( $route )
  {
    $route = rtrim( $route, '/' );
    $this->request = explode( '/', $route );
  }

  private function GetAction()
  {
    if( isset( $this->request[1] ))
      return $this->request[1];
    else
      return self::default_action;
  }

  private function GetParams()
  {
    if( count( $this->request ) > 2 )
      return array_slice ( $this->request, 2 );
    else
      return array();
  }

  private function GetPost()
  {
    return $_SERVER['REQUEST_METHOD'] == 'POST';
  }

  private function GetController()
  {
    if( isset( $this->request[0] ))
      return $this->request[0];
    else
      return self::default_controller;
  }

  private function GetRequest()
  {
    return $this->request;
  }

【讨论】:

  • 我不太习惯使用您的代码。它包含很多我在我的代码中并不真正想要的全局空间。另外,您是否考虑过使用 cmets?该代码不是不言自明的。我还是会看看的。
  • @JvdBerg stackoverflow.com/questions/5166087/php-global-in-functions/…。此外,您的路由器不应该知道有关 SAPI 的任何信息。从外部传入 URL。
  • 谢谢戈登。我更改了路由器类,将 SAPI 和全局变量移至引导程序。添加了一些 cmets
  • 错误检查不足。很少,如果有的话。做了太多假设。
  • 一行评论还不足以解释一个新手发生了什么。
猜你喜欢
  • 1970-01-01
  • 2012-11-19
  • 1970-01-01
  • 2016-12-21
  • 2018-02-12
  • 2018-02-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多