如何将基于页面的PHP应用程序转换为MVC模式?

11

我一直在思考如何使用MVC框架重构基于页面的PHP应用程序。背景是因为我的老板要求我这么做。我已经列出了目录结构并尝试规划如何将这些页面转换为控制器/操作对。有些东西看起来很简单,比如有一些页面专门用于添加/编辑/删除用户。这很容易创建一个“用户”控制器,并添加添加/编辑/删除等方法或操作。但我面临的问题是何时实际上创建一个控制器而不是只是将它作为操作,因为这并不总是那么明确。例如,登录控制器与用户/登录,或者注册控制器与用户/注册。对我来说,如果对象可以执行某些操作,那么将其作为操作就很合理,但这并不总是那么明确。

另一个例子是,我有大约12个表单页面用于创建“计划”。在我的想法中,我认为我需要创建一个“计划”控制器,然后每个旧的表单页面都会成为一个操作。所以我会有一个具有12个操作(方法)的控制器。问题在于,虽然这12个页面都是数据输入表单,最终组成了这个“计划”,但它们唯一相同的是这一点。每个页面在数据库中使用不同的表,除此之外没有任何共同点。基本上,通过创建一个“计划”控制器,我只是将它作为分组机制使用;不一定是因为它们彼此相关。至少在上面的“用户”控制器示例中,每个操作都使用相同的“用户”表,因此将这些操作分组到一个控制器中是有意义的。那么,我应该将每个数据输入表单都创建为自己的控制器吗?

我想这就是让自己把控制器作为层次结构实体而不是对象/操作的问题。只是似乎很容易陷入错误地使用控制器的陷阱。有人明白我在说什么吗?希望这不会太混乱。

编辑:如果我尝试坚持每个视图一个控制器的做法,那么我将使每个请求的代码最小化。这是最好的方法吗?

编辑:从所有人的说法来看,似乎每个视图一个控制器并不符合我的最佳利益。我仍然有一些担忧,因为控制器可能很快变得臃肿,但这是另一个讨论的问题。我还有一些关于何时决定使用控制器而不是动作的问题。一个很好的例子就是StackOverflow本身。在页面顶部,你有一个“问题”选项,我们可以认为它带你到“问题”控制器。我之所以这么说,是因为在右侧你可以选择“提问”,它的URL指向“questions/ask”。这意味着你正在使用“questions”控制器的“ask”方法。令我困惑的是菜单中的“未回答”选项。它看起来像是有一个自己的控制器。为什么它不只是“questions/unanswered”下的一个动作呢?这就是我感到困惑的地方。


我真的希望这个要求是因为他们计划在未来用这个应用程序做大事情,而不仅仅是因为MVC是最新的开发热词。如果他们只是这样做,那么这将是一场巨大的浪费时间,并可能会产生大量新的错误。 - TravisO
我认为这个小丑(乔)太过于“元数据”而不是足够的“实现”。 - theman_on_vista
11个回答

4

如果你的老板喜欢使用时髦的术语,那就告诉他查一下“重构”。


2
略微偏离你的问题……上帝啊,请不要忘记你的旧URLS。原因是,一旦你将新网站和它漂亮的URLS切换过来,所有由搜索引擎持有的聚合内容都会逐渐过期,从而大幅降低你的SEO统计数据。
更新:如果我尝试坚持每个视图一个控制器……那么我将尽量减少每个请求的代码,但这是最好的方法吗?
如果我理解正确,每个控制器都会生成一个页面。这可能是一个非常糟糕的想法,我在维护职位上亲身经历过。如果您有与OO范例不太匹配的内容,则将其归类为:新闻发布、单次操作、信息等,并将其放入仅提供这些视图的控制器中,或者更好地说,将其放入每个类别的多个控制器中以留下更精细的控制。
我处理过的“MVC”框架很快就变成了一个混乱的烂摊子,有很多hack和意大利面式的代码。
我有大约12个表单页面用于创建“计划”。
我曾经不得不做类似的事情,尽管看起来很傻,但拥有一个“planningform”控制器可能是最好的选择。是的,有多个表需要提供数据,但你可以做一些简单的事情,比如$_SESSION['plannerController']['subject|action'][key][value],以保持整个表单的每个部分都在检查中。每个方法处理多页表单的一部分也可以是一个好处(例如,如果你的老板说他们只需要12页中的6页,或者第7页变得非常复杂,需要像自动完成ajax之类的东西)。
不一定要使用分组机制,因为它们与彼此相关。
我通常喜欢一个类、一个目的的范例,但有时候这样做并没有意义。有些情况是清晰明了的,直接按照教科书上的OO来做,而其他时候则可以将学术BS放到一边。你会发现一些不符合OO模式的东西,所以你可以退后一步,尝试将它们合并到共同的类别中。

2
每个控制器只有一个视图的好处是什么?
1. 每个请求只加载必要的代码,不会多余加载库、包或dll文件。因此,不要创建一个巨大的控制器文件,在每个请求中只执行其中一小部分代码。同时,较小的文件更易于维护,就像较小的方法和模块化的代码一样。 2. 控制器和视图之间有明确的1:1关系(特别是如果它们具有相同的名称)。这是一种约定。这是清晰而一致的。如果我在查看视图模板,则仅根据文件名就可以知道负责加载它的控制器。无需思考、决策或妥协。
当所有内容都成对匹配时,为什么需要关注点分离?
如果您想将相关页面(控制器/视图)分组,请将它们放入目录中。
我更倾向于编写一个控制器来控制几个相关的视图。例如,前面提到的添加/查看/编辑用户。您希望将类似的功能放在一起,而不是在许多文件中搜索所需的代码。也很方便将所有方法(针对特定对象)定义在一个地方。这样维护就容易得多。
我不同意这种做法。如果我有一个单独的视图负责添加/查看/编辑用户,则根据1:1约定,我知道负责的控制器是哪个。另一方面,如果按照您的建议将类似的功能分组,如果我有一个管理器控制器和一个用户控制器,哪一个包含管理器的添加/查看/编辑?用户或管理员?现在你必须思考或搜索。
我曾经参与过使用PHP框架的项目,该框架为每个“操作”创建了一个单独的文件,命名方式为“对象(操作)”,这变成了一场噩梦,难以维护。
我不建议这样做。
我已经使用Django一段时间了,它将所有模型放在一个文件中,将所有视图(控制器)放在一个文件中,并将模板(视图)分开。[Django不是MVC,但出于这些目的,让我们假装它是].这可以让您将常见代码组合在一起,使维护变得更加容易。
我感到有点晕了。我不了解Django,也假设单个文件是可选的,但是我无论如何都不会维护数万行的文件。
我的唯一建议是-不要尝试根据MVC的理想来组织项目。根据您和您的领域的实际情况组织您的项目。

不,不,不。那是非常危险的建议。设计模式、编码规范和框架被设计出来是有目的的——最佳实践和一致性。只有大师才应该违背惯例,只有在他/她独自工作时才能这样做。即使在框架的限制下,我也不断努力实现更大的一致性,这样我在编写或维护代码时就不必思考。


1
在进行这样的移动时,你不仅仅是改变了特定网站的实现方式,而是改变了它被感知的方式。无论如何,你都需要大量重新编码,但你有理解概念和经验的优势。
我的建议是坐下来假装你对如何实现网站一无所知,但对你正在处理的网站类型的问题——它的要求和难点——非常了解。创建一个基于MVC的网站概念,并尽可能重用你已有的代码。代码重用可能最终并不可行,但好的一面是,至少你之前已经解决了许多相同的问题,因此实现起来不应该花费太长时间。
不要忘记,你始终可以使用路由来维护合理的URL结构,例如使用不同控制器的一系列表单页面,允许你使用不同的控制器、操作和视图,同时仍然能够借鉴相同的布局或模板。

1

每个控制器只有一个视图的意义是什么?当一切都是成对匹配时,为什么需要关注点分离?

我更倾向于编写一个控制多个相关视图的控制器。例如,前面提到的用户添加/查看/编辑。您希望将类似的功能放在一起,而不是在许多文件中搜索所需的代码。在一个地方定义所有方法(针对特定对象)也很方便。这使得维护变得容易得多。

我曾经参与过一个使用PHP框架的项目,该框架为每个“操作”创建了一个单独的文件。命名方式为“object(action)”,这变成了一个噩梦般的维护。

我现在已经使用Django一段时间了,它将所有模型放在一个文件中,所有视图(控制器)放在一个文件中,而模板(视图)则分开。[Django不是MVC,但出于这些目的,让我们假装它是]。这样可以将常见代码组合在一起,维护变得容易得多。

我的唯一建议是-不要尝试根据MVC的理想来组织项目。根据您和您的领域的实际情况组织您的项目。MVC是为了限制复杂性而提出的,而不是增加它。

对于您的计划示例,我会将其制作成一个控制器。www.example.com/plan/1 .. /plan/2等。这是有道理的,因为这些操作在逻辑上被分组在一起以完成某个任务。
在我看来,控制器应该用于管理任务或对象。它的方法应该是完成该任务或修改/使用对象所需的特定操作。

1

去年我也经历了类似的事情。我将我的大部分静态PHP网站页面转换为使用Kohana PHP框架。我将每个网站部分作为控制器,具有单独页面的视图。主页视图包括其他视图,如页眉和页脚。一些视图,例如文章视图,可以被不同的控制器重复使用。结果是一个MVC网站,具有与原始网站相同的页面URL。

编辑:URL的格式为/controller/method?arguments。例如,在我的网站上,/computer/article.php?id=#的URL使用computer控制器中的article函数。computer控制器反过来使用article模型将数据加载到嵌套的paragraph视图中的article视图中。这也说明,即使先前版本的网站在URL中具有.php扩展名的页面名称,这仍然可以转换为控制器类方法,并且相同的URL在基于MVC的网站版本中起作用。这应该让您了解Kohana如何适用于您的网站。


1
我还有一些问题,关于什么时候使用控制器而不是动作。一个很好的例子就是这个stackoverflow网站。在页面顶部,你有一个“问题”选择,我们可以假设它会带你到“问题”控制器。我这么说是因为在右边,你可以选择“提问”,URL指向“questions/ask”。所以,使用了问题控制器的询问方法是有意义的。
但是,我们不知道“ask”是否是“questions”控制器的动作。这个站点可能会将“questions/ask” URL路由到其他控制器,或者“ask”可能是“questions”目录中的一个控制器。此外,如果你打开“questions/ask”页面,你会注意到表单提交到“questions/ask/post”。现在,“post”可能是“ask”动作方法的参数,但我猜它是“ask”控制器的“post”动作方法。
谁知道呢。但请考虑这一点: “问题”页面需要比“提问问题”页面多几倍的代码。在加载“提问问题”页面时加载“问题”页面的代码是否有意义?我认为stackoverflow的人会更聪明。
但是让我困惑的是,菜单上有一个“未解决”的选项。它看起来好像有自己的控制器。为什么它不只是问题控制器下面的一个动作呢?例如“questions/unanswered”?这就是让我感到混乱的地方。
我认为“提问”页面有自己的控制器的另一个原因是,“问题”页面与“未解决”页面有很多共同之处。

0
如果我理解正确的话,每个控制器都会生成一个页面。这可能是一个非常糟糕的想法,我在维护职位上亲身经历过。

我处理的“MVC”框架采用了一种控制器到视图的方式,很快就变成了一个混乱的烂摊子,有很多hack和意大利面式的代码。

这是因为框架、开发者还是两者都有?它难以维护在哪里?涉及哪些hack以及原因是什么?

对我来说,在php MVC框架Zend、CodeIgniter和Kohana中,1个控制器对应1个视图效果很好。在ASP.NET中,虽然不是MVC,但1个Web表单/视图映射到1个代码后台文件。


0
整个概念在Nemetral的三篇博客文章中变得更加清晰,这些文章将该模式从传统的Web页面脚本发展到PHP逻辑与表示逻辑分离的阶段,因此在MVC方面,模型与视图是如何分离的。 根据Nemetral:
  1. 第一步是将到目前为止与HTML标记耦合的所有PHP代码移动到页面的头部。
  2. 第二步是将所有HTML标记移到单独的文件中,并通过PHP include访问它。因此,当请求被发出时,它被引导到PHP代码(控制器和模型),然后该代码请求HTML标记或表现(视图)。
我在我的论文中写得更多:http://kreus-cms.com/kreus/pages/written_work

0

假设您已经准备好了一些测试,覆盖了应用程序的大部分(全部?)功能。

  1. 构建一个基本结构来描述你的C部分会是什么样子。
  2. 忽略M和V实现这个结构,更或者将你页面导向文件中的代码片段复制到控制器中
  3. 测试确保一切都按预期工作
  4. 浏览每个控制器,提取一个“M”和一个“V”。 “M”可以只是Active Record(我知道,这不是真正的模型)或行表网关或某些相当快速实现的东西。控制器现在应该变得越来越瘦
  5. 测试确保一切都按预期工作
  6. 现在您已经对应用程序有了全面的了解,如果适用,可以再次重构以提取域模型。 在某些情况下,这可能只是浪费时间(仅限于CRUD /密集型应用程序)
  7. 测试确保一切都按预期工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接