【问题标题】:How do I effectively implement modules in an MVC framework, and handle routing to multiple Controllers in a single module?如何在 MVC 框架中有效地实现模块,并在单个模块中处理到多个控制器的路由?
【发布时间】:2012-10-10 01:57:09
【问题描述】:

我已经开发了一个基本的 MVC 框架作为 php 中的一个学习项目——这实际上是它的第二个版本,我正在尝试改进第一个版本的两个不足:

  • 请求路由: 映射请求,例如/controller/action/[参数]
  • 模块: 旨在扩展应用程序的插入式应用程序,例如CMS。

这就是我现在所处的位置:

  1. 我能够接受一个请求并将其解析为各个部分, 例如控制器、动作、参数等。这些映射到对应的 控制器类/文件,例如“/foo/bar” -> FooController::bar() - 所有这些都在我的 RequestRouter 类中完成并封装在一个 请求对象。

    • 我维护了一个清单对象,其中包含对应用程序文件的分类引用(控制器、库等)。我的自动加载器方法使用了清单。
    • 由于清单已缓存,因此每当我添加新文件/类时都会重新构建它,当添加/删除新模块时也是如此。
  2. Controller::methods() 可以很好地渲染正确的视图。

  3. 然后是模块,它们像核心一样被组织起来 (/root/raspberry/vendors/core/module)

问题

我认为我目前遇到的问题是涉及模块的路由/请求处理的组合:

  • 如果我请求 project.dev/admin 它映射到 AdminController::index() -- 这是正确的
  • 但是,当我引用 project.dev/admin/editor 时,我仍然得到 AdminController::editor() 我真正想要的是 EditorController::index()

经过一番研究,我想我可以创建一个装饰器,它实现了一个前端控制器模式并包装了一个给定的控制器。装饰器可以重新解析请求以使 /editor 成为控制器并重新映射剩余的段(/editor/action/args)。

所有这些似乎都可以正常工作,但我觉得我在流程的早期缺少一些基本的东西(RequestRouter)。我在 SO 中研究了其他类似的问题,并阅读了 HMVC,原则上它似乎可以回答我的问题,但它似乎比框架驱动更接口驱动(如果这有意义?)我已经还查看了其他框架,例如 Kohana,但我不太了解它们的模块系统和路由到同一模块中的多个控制器的工作原理。

任何关于如何在不引入前端控制器或重新解析请求的情况下有效实施模块系统的见解或建议将不胜感激。或者,如果我应该以不同的方式重新构建我的模块,我想了解如何做到这一点。

附加信息:

我的 RequestRouter 维护着我预定义的路由列表(包括它们的默认方法)。使用这些预定义的路由,我可以访问 /admin/editor 并获取 EditorController::index(),但我必须为每个控制器定义一个路由,并请求发送到模块。我不认为这是一个好的设计。这是我的路线示例:

Array
(
    [/foo] => Array
        (
            [controller] => FooController
            [method] => bar
            [path] => /core
        )

    [/admin] => Array
        (
            [controller] => AdminController
            [method] => index
            [path] => /vendors/admin
        )

    [/admin/editor] => Array
        (
            [controller] => EditorController
            [method] => index
            [path] => /vendors/admin
        )

)

这就是我的 Request 对象的样子:

Request Object
(
    [properties:Request:private] => Array
        (
            [url] => /admin/editor
            [query] => 
            [uri] => /admin/editor
            [controller] => admin
            [action] => editor
            [args] => 
            [referrer] => Array
                (
                    [HTTP_REFERER] => 
                    [REMOTE_ADDR] => 127.0.0.1
                    [HTTP_VIA] => 
                    [HTTP_X_FORWARDED_FOR] => 
                )

            [get] => 
        )

    [request_status:Request:private] => 200
)

这是我的清单示例:

[controller] => Array
    (
        [icontroller] => /htdocs/raspberry/raspberry/core/controller/icontroller.class.php
        [index] => /htdocs/raspberry/raspberry/core/controller/index.php
        [serviceerror] => /htdocs/raspberry/raspberry/core/controller/serviceerror.controller.php
        [admin] => /htdocs/raspberry/raspberry/vendors/core/admin/controller/admin.controller.ph
        [composer] => /htdocs/raspberry/raspberry/vendors/core/admin/controller/composer.controller.php
    )

这是应用程序文件系统:

http://s11.postimage.org/pujb2g9v7/Screen_shot_2012_10_09_at_8_45_27_PM.png

【问题讨论】:

    标签: php model-view-controller module design-patterns vendor


    【解决方案1】:

    这个问题似乎是由过度简化的路由机制引起的。我得到的印象是您正在使用简单的explode() 从 URL 收集参数。虽然这在基本示例中正常工作,但当您尝试使用更高级的路由方案时,设置将失败。

    您应该将其与正则表达式模式匹配,而不是在 / 上拆分字符串。基本上,您定义要匹配的模式列表,并使用第一个匹配来填充 Request 实例。

    在您的情况下将定义两种模式:

    • '#admin/(:?(:?/(?P<controller>[^/\.,;?\n]+))?/(?P<action>[^/\.,;?\n]+))?#'
    • '#(:?(:?/(?P<controller>[^/\.,;?\n]+))?/(?P<action>[^/\.,;?\n]+))?#'

    如果第一个失败,第二个会匹配。

    P.S. 你应该知道控制器不应该呈现输出。响应的生成是视图实例的责任。视图应该是功能齐全的对象,其中包含表示逻辑并且可以从多个模板组成响应。

    【讨论】:

    • 感谢 tereško 的回复——对你的后一点:是的,我的控制器现在只输出视图,这样我就可以从头到尾看到一个请求(我没有任何模型目前已定义,因为我仍在尝试解决此问题。) - 至于路由,您是对的,因为我正在使用 explode() 作为请求解析器的一部分。所以,我从您的回复中了解到,我应该使用正则表达式来解析请求。我是否必须为每个模块或模块中对控制器的每个请求创建一系列模式?
    • @user916011 ,那么您也只需为匹配模块创建一个模式。像(:?(:?(:?/(?P<module>[^/\.,;?\n]+))?/(?P<controller>[^/\.,;?\n]+))?/(?P<action>[^/\.,;?\n]+))? 这样的东西。它将匹配 action 、 action+controller 或 action+controller+module。但是您必须对其进行测试。
    • 我听取了您的建议,并采用了正则表达式方法,解析了查询字符串,同时保留了可用于匹配请求的运行模式列表。我无法完全破译你的正则表达式字符串(我在那里有点缺乏经验......),所以我自己写了:#^(?:\/([a-z0-9-_]+)\/([a-z0-9-_]+)\/([a-z0-9-_\/]+))(?:\?([^\s]+)|$)?##^\/admin(?:\/([a-z]+)\/([a-z]+)\/([a-z0-9-_\/]+))(?:\?([^\s]+)|$)?#。模式(虽然可能效率低下,但解析请求的各种版本。我采用 first 匹配(不一定是 best 匹配)。
    • 如果找不到匹配项,我可以回退到基本路由。但我知道常见模式存在问题,例如查询字符串解析与处理domain.com/.
    • @user916011 ,您应该以 first match 为最佳方式设置路由。很容易测试。至于domain.com/,只需使用$_SERVER['PATH_INFO']$_SERVER['QUERY_STRING']。这将为您提供域名之后的部分或? 符号之后的部分。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-10
    相关资源
    最近更新 更多