召唤专家:重构故事(第3/5部分)
类中的一些操作将从重构开始产品
模块。
由于s欧宝娱乐app下载地址ymfony是在MVC设计模式的基础上建模的,Vince对自己的关注点分离做得很好很有信心。
控制器角色是从模型中获取数据并将其传递给视图。
很简单,不是吗?
很多人都知道这个理论,但在实践中却很难做到不折不扣;很容易让一些业务逻辑滑入控制器。文斯也不例外。
瘦控制器,胖模型
网站的主页由所有可用产品的列表组成,管理该页面的当前代码包含在指数
行动:
/ /应用程序/前端/模块/产品/ actions.class.php公共函数executeIndex(){//获取所有可用的产品美元标准=新标准();美元标准->添加(ProductPeer::IS_IN_STOCK,真正的);美元标准->addAscendingOrderByColumn(ProductPeer::价格);美元标准->setLimit(10);这个美元->产品= ProductPeer::doSelectJoinCategory(美元标准);这个美元->getResponse()->setTitle(“所有产品”);这个美元->getResponse()->addStylesheet(“homepage.css”);返回sfView::成功;}
但是,标准
的定义和调用doSelectJoinCategory
不属于控制器,它属于模型。
文斯决定把这段代码转移到ProductPeer
通过创建getAvailableProducts ()
方法:
/ / lib /模型/ ProductPeer.php静态公共函数getAvailableProducts(){美元标准=新标准();美元标准->添加(自我::IS_IN_STOCK,真正的);美元标准->addAscendingOrderByColumn(自我::价格);美元标准->setLimit(10);返回自我::doSelectJoinCategory(美元标准);}
控制器现在可以直接使用这个新方法:
/ /应用程序/前端/模块/产品/ actions.class.php公共函数executeIndex(){这个美元->产品= ProductPeer::getAvailableProducts();这个美元->getResponse()->setTitle(“所有产品”);这个美元->getResponse()->addStylesheet(“homepage.css”);返回sfView::成功;}
与前面的代码相比,这种重构有几个好处:
- 获取可用产品的逻辑现在在Model中,它属于这里
- 控制器中的代码可读性更强
- 代码可读性很强,我们不需要再编写文档了
- 的
getAvailableProducts ()
方法是可重用的(在另一个操作中,或在任务中,…) - 模型代码现在是单元可测试的
- 两个开发人员可以一起工作:
- 一个负责对模型进行编码,并从中创建公共API
- 另一个使用模型公共API来编码控制器。她也不关心如何检索产品(数据库、XML、Web服务……)
但是,如果您在另一个操作中需要所有可用产品的列表,或者如果您只需要获得前五个产品,该怎么办?您将希望重用相同的方法,但就像现在一样,它不是非常灵活的限制(10
)是硬编码的。
现在保持这种方式很好,但一旦Vince需要获得相同类型的结果,但条件略有不同,他就需要重构新方法以添加一些参数:
/ / lib /模型/ ProductPeer.php静态公共函数getAvailableProducts(美元最大=10){美元标准=新标准();美元标准->添加(自我::IS_IN_STOCK,真正的);美元标准->addAscendingOrderByColumn(自我::价格);美元标准->setLimit(美元最大);返回自我::doSelectJoinCategory(美元标准);}
你懂的。目标是能够在不同的上下文中重用相同的代码。第一步是在正确的层中重构代码,然后通过添加一些参数添加自定义结果的方法。
这个很简单。让我们重构代码的另一部分。
瘦控制器,胖模型,重访
现在来看看最喜欢的管理代码。
在收藏夹中添加和删除产品的代码可以在产品
模块:
/ /应用程序/前端/模块/产品/ actions.class.php公共函数executeAddToFavorites(){美元的产品= ProductPeer::retrieveByPk(这个美元->getRequestParameter(“id”));这个美元->forward404Unless(美元的产品);美元的最爱=这个美元->getUser()->getAttribute(“最爱”);美元的最爱[美元的产品->getId()]=真正的;这个美元->getUser()->setAttribute(“最爱”,美元的最爱);这个美元->重定向(“产品/指数”);}公共函数executeRemoveFromFavorites(){美元的产品= ProductPeer::retrieveByPk(这个美元->getRequestParameter(“id”));这个美元->forward404Unless(美元的产品);美元的最爱=这个美元->getUser()->getAttribute(“最爱”);设置(美元的最爱[美元的产品->getId()]);这个美元->getUser()->setAttribute(“最爱”,美元的最爱);这个美元->重定向(“产品/指数”);}
正如您自己所看到的,收藏夹存储在用户会话中。但是,这个代码中嵌入了太多的逻辑。控制器不需要知道收藏夹存储在哪里以及如何存储。
文斯决定把这段代码转移到myUser
类通过创建方法来封装逻辑:
/ /应用程序/前端/ lib / myUser.class.php公共函数addProductToFavorites(产品美元的产品){美元的最爱=这个美元->getAttribute(“最爱”);美元的最爱[美元的产品->getId()]=真正的;这个美元->setAttribute(“最爱”,美元的最爱);}公共函数removeProductFromFavorites(产品美元的产品){美元的最爱=这个美元->getAttribute(“最爱”);设置(美元的最爱[美元的产品->getId()]);这个美元->setAttribute(“最爱”,美元的最爱);}
的重构动作产品
module类现在使用这个新的用户API:
/ /应用程序/前端/模块/产品/ actions.class.php公共函数executeAddToFavorites(){美元的产品= ProductPeer::retrieveByPk(这个美元->getRequestParameter(“id”));这个美元->forward404Unless(美元的产品);这个美元->getUser()->addProductToFavorites(美元的产品);这个美元->重定向(“产品/指数”);}公共函数executeRemoveFromFavorites(){美元的产品= ProductPeer::retrieveByPk(这个美元->getRequestParameter(“id”));这个美元->forward404Unless(美元的产品);这个美元->getUser()->removeProductFromFavorites(美元的产品);这个美元->重定向(“产品/指数”);}
这种重构与前面的重构具有相同的好处,但有一个额外的好处:如果我们决定更改属性名称,或者希望在数据库中存储收藏夹,则控制器代码不会更改。
模板也是如此。下面是处理收藏夹的代码片段:
/ /应用程序/前端/模块/产品/模板/ indexSuccess.php<?php如果(in_array(美元的产品->getId(),中的(sf_user美元->getAttribute(“最爱”,数组())))):? ><?php回声link_to(image_tag(“/图片/ favorite.png”),“产品/ removeFromFavorites ?id = '.美元的产品->getId())? ><?php其他的:? ><?php回声link_to(“添加到我的收藏夹”,“产品/ addToFavorites ?id = '.美元的产品->getId())? ><?phpendif;? >
第一行测试产品是否在当前用户收藏夹中,以显示“添加”链接或“删除”链接。
在这里,我们需要通过创建另一个方法将这段代码移动到用户类:
/ /应用程序/前端/ lib / myUser.class.php公共函数hasInFavorites(产品美元的产品){返回in_array(美元的产品->getId(),中的(这个美元->getAttribute(“最爱”,数组())));}
下面是清理后的模板片段:
/ /应用程序/前端/模块/产品/模板/ indexSuccess.php<?php如果(sf_user美元->hasInFavorites(美元的产品)):? ><?php回声link_to(image_tag(“/图片/ favorite.png”),“产品/ removeFromFavorites ?id = '.美元的产品->getId())? ><?php其他的:? ><?php回声link_to(“添加到我的收藏夹”,“产品/ addToFavorites ?id = '.美元的产品->getId())? ><?phpendif;? >
我们又做了一次。由于重构和我们选择的方法名,代码可读性更强。这是非常重要的考虑一下方法名.它们传递了大量的信息,大多数时候,它们可以取代文档。欧宝体育电话你不需要解释什么sf_user - > hasInFavorites(产品)
所做的事。
重构之后,处理收藏夹的所有代码现在都在同一个类中。这是另一个好处。现在,所有内容都集中在一个类中,而不是在许多不同的地方操作收藏夹。您甚至可以通过创建一个专用的ProductFavorite
如有需要,请进行分类。
今天就到这里。下一次,Vince和我将重构允许修改产品和相关图像的操作。同时,您可以将我们今天学到的规则应用到您自己的symfony项目中。欧宝娱乐app下载地址
评论
评论截止。
为了确保评论保持相关性,旧帖子将被关闭。
1)新产品的创建也应该写在ProductPeer中吗?我想答案是肯定的。
2)插件带来的类呢?例如,sfGuardUser:我们应该在哪里添加特定的代码,因为我猜它不会很好硬编码到sfGuardPlugin目录。
1)是的。
2)创建自己的类,扩展插件提供的模块。
但如果我有两个插件CommentPlugin和PollPlugin,我使用sfGuardPlugin。
我需要有两个方法:我需要测试,用户是否已经阅读了一些评论,我需要测试,用户是否已经在投票中投票。
我不能扩展sfGuardSecurityUser,因为我不知道,在哪个顺序我将使用插件,哪一个将从另一个扩展。
这是因为我创建了一些类似于“ProducFavorite”-“PollVoter”,“CommentReader”的操作与sfContext::getUser()…
谢谢你的建议,这是一个非常有用的教程。
$product = ProductPeer::retrieveByPk($this->getRequestParameter('id'));
美元$ this - > forward404Unless(产品);
到preExecute函数
而且
$ this - >重定向(“产品/指数”);
到postExecute ?
因此executeAddToFavorites()和executeRemoveFromFavorites()都被缩小为一行。
{
返回in_array($product->getId(), array_keys($this->getAttribute('favorites',数组())));
}
我最好这样写:
public函数hasfavorites (Product $ Product)
{
$favorites = $this->getAttribute('favorites',数组());
返回收取(最爱美元($产品- > getId ()));
}
这个微小的优化允许代码在O(1)而不是O(n)(其中n是当前收藏夹的数量)中执行。