mvc的三层架构,mvc三层结构
在第二天的学习中,我们学习了如何基于关系数据模型构建对象模型,并为其中一个对象生成了程序框架。顺便说一下,前一天生成的程序代码可以在askeet的SVN仓库获得:
http://svn.askeet.com/
我们第三天的目标是为这个网站定义一个漂亮的结构布局,以问题列表为默认主页,显示对某个问题感兴趣的用户数量,将样本文本文件迁移到数据库进行数据测试。要做的事情不多,但是要读要懂的东西很多。
为了阅读本教程,我们需要熟悉Symfony中解释的工程、过程、模块和动作的概念。
MVC模式
今天是我们第一次进入MVC架构的世界。这是什么意思?简单来说,生成页面的代码根据其特性位于不同的文件中。
如果代码独立于页面专注于数据的操作,那么它应该位于模型中(大多数情况下,在askeet/lib/model/目录中)。如果他专注于最终显示,他应该位于视野中。在Symfony中,视图层基于模型(如Askeet/Apps/Frontend/Modules/Question/Templates/)和配置文件。最后,将所有这些链接在一起并将网站逻辑转换为PHP代码的程序代码位于控制器中,而在Symfony中,指定页面的控制器称为动作。我们可以在symfony中的MVC实现一章中了解更多关于这个模型的内容。
然而今天,我们的程序只做了一些小的改动,我们将操作许多不同的文件。不用麻烦,因为文件组织与不同代码层的分离很快就会变得明显和有用。
更改布局
在设计模式中,由操作调用的模板内容被集成到全局模板或布局中。换句话说,布局包含了所有界面的不变部分,并且修饰了动作的结果。打开默认布局(askeet/apps/frontend/templates/layout . PHP)并将其更改为以下内容:
! DOCTYPE html PUBLIC -//W3C//DTD XHTML 1.0 Transitional//EN http://www . w3 . org/TR/XHTML 1/DTD/XHTML 1-Transitional . DTD
html xmlns= http://www . w3 . org/1999/XHTML XML:lang= en lang= en
头
?php echo include_http_metas()?
?php echo include_metas()?
?php echo include_title()?
link rel=快捷图标 href=/favicon.ico /
/头
身体
div id=header
保险商实验所
李?php echo link_to(about , @homepage )?/李
/ul
h1?PHP echo link _ to(image _ tag( askeet _ logo . gif , alt=askeet ), @homepage )?/h1
/div
div id=content
div id=content_main
?PHP echo $ SF _ data-getRaw( SF _ content )?
分区/分区
/div
div id=content_bar
!-暂时没有-
分区/分区
/div
/div
/body
/html
我们尽可能让标记易于理解,并将所有样式转移到CSS样式表中。这里我们将不描述样式文件,因为CSS语法不是本教程的目的。我们可以在SVN仓库下载这些风格文件。
我们创建了两个样式文件(main.css,layout.css)。将它们复制到我们的askeet/web/css/目录,并编辑我们的frontend/config/view.yml文件以更改自动加载的样式文件:
样式表:[主,布局]
这个时候布局还是轻量级的,后面我们会重新构建。模板中很重要的一点是head部分,它是自动生成的,sf_content变量包含了动作的结果。
我们可以通过请求主页来检查修改是否可以正确显示——这次是在开发环境中:
http://askeet/frontend_dev.php/
环境的描述
如果我们想知道http://askeet/frontend_dev.php/和http://askeet/的区别,那么我们需要看一下Symfony的配置部分。现在我们只需要知道它们指向的是同一个程序,只是环境不同而已。一个环境是一个独特的配置,此时框架的特性可以根据需要决定是否激活。
在这种情况下,/frontend_dev.php/URL指向开发环境。此时,将在每个请求时解析整个配置,HTML缓存未激活,调试工具可用(包括窗口右上角的半透明工具栏)。而/URL——相当于/index . PHP/——指向生产环境。此时,配置被“编译”,并且为了加速页面传输,调试工具栏被禁用。
这两个PHP脚本——frontend _ dev.php和index . PHP——被称为前端控制器,所有对程序的请求都由它们处理。我们可以在askeet/web/目录中找到它们。实际上,index.php应该被命名为frontend_dev.php,但由于frontend是我们创建的第一个程序,Symfony推测我们可能希望它作为默认程序,所以我们将其重命名为index.php,这样我们就只能在生产环境中请求/来查看我们的程序。如果想了解更多通常MVC模型中的前端控制器和控制器层,可以查看Symfony中的控制器章节。
一个好的规则是,我们在开发环境中浏览,直到我们对所有的特性都满意,然后切换到生产环境,检查它的速度和良好的URL。
记住,当我们添加一个新的类或者更改配置文件时,为了在生产环境中查看结果,我们应该首先清除缓存。
重新定义主页
现在,当我们请求新网站的主页时,他显示一个“祝贺”页面。一个更好的主意是显示一个问题列表。为此,打开前端程序的路由配置文件(askeet/apps/frontend/config/routing . yml),导航到homepage:部分并进行更改:
主页:
网址:/
param: {模块:问题,操作:列表}
在开发环境中刷新页面(http://askeet/frontend_dev.php),现在会显示问题列表。
如果我们是一个好奇的人,我们可能会寻找带有“祝贺”信息的页面。如果在askeet目录中找不到这个文件,我们会很惊讶。其实default/index动作的模板是在Symfony的数据目录中定义的,它是独立于项目的。如果我们想覆盖它,我们可以在我们自己的目录中创建一个默认模块。
路由系统提供的功能后面会详细讨论,但是如果感兴趣可以看Symfony中routing这一章。
定义测试数据
主页的列表显示仍然是空的,除非我们添加自己的问题。当我们开发一个程序时,拥有一些测试数据是一个好主意。手动输入测试数据相当痛苦,这也是Symfony可以使用文件迁移到数据库的原因。
我们将在目录askeet/data/fixtures/(需要创建这个目录)中创建一个测试数据文件。使用以下代码创建名为test_data.yml的文件:
用户:
匿名:
昵称:匿名
名字:匿名
姓氏:懦夫
法比安:
昵称:法布波特
名字:杨奇煜
姓氏:权力者
弗朗索瓦:
昵称:弗朗索瓦
名字:弗朗索瓦
姓氏:扎尼诺托
问题:
q1:
标题:今晚我和女朋友做什么?
用户标识:法比安
正文:
晚饭前我们在邓肯甜甜圈前见面,
我完全不知道该拿她怎么办。
她对编程、太空歌剧电影和昆虫都不感兴趣。
她挺可爱的,所以我真的需要找些东西
那会让她在我身边再呆一个晚上。
第二季度:
标题:我能给我的继母提供什么?
用户标识:匿名
正文:
我的继母拥有继母通常能得到的一切
(手表,吸尘器,耳环,del.icio.us账号)。
她的生日就在下周,我破产了,我知道
如果我不给她一些甜的东西,我的女朋友
一个月都不会看我的眼睛。
q3:
标题:我怎样才能给我的博客带来流量?
用户标识:弗朗索瓦
正文:
我有一个非常棒的博客
关于我的班级、朋友、宠物和最喜欢的电影。
兴趣:
i1: {用户id: fabien,问题id: q1 }
i2: {用户id: francois,问题id: q1 }
i3: {用户标识:弗朗索瓦,问题标识:q2 }
i4: { user_id: fabien,question_id: q2 }
首先,也许我们会在这里认出YAML语法。如果我们不熟悉Symfony,那么也许我们不知道YAML格式是框架中最喜欢的配置文件格式。这不是唯一的——如果我们也可以使用XML或。ini文件,我们可以很容易地添加一个配置处理器来允许Symfony读取它们。如果我们有时间和耐心,我们可以在Symfony的实际配置一章中读到更多关于YAML和Symfony的配置。现在,如果我们不熟悉YAML语法,那么我们需要立即开始,因为本教程广泛使用YAML语法格式。
好了,现在回到测试数据文件。他定义对象实体并通过其内部名称来识别它。这个标签对于链接相关对象很有用,不需要定义id。例如,创建的第一个对象是User类,标识为fabien。第一个问题标识为q1。通过指定相关的对象标签,很容易创建一个类的对象:
兴趣:
i1:
用户标识:法比安
问题id: q1
上面给出的数据文件使用简短的YAML语法来描述这些内容。我们可以在Symfony的数据文件章节中了解更多关于数据迁移的信息。
我们不需要为created_at和updated_at列定义值,因为Symfony知道默认情况下如何填充这些字段。
创建一个批处理文件来迁移数据。
下一步是实际迁移数据库,我们希望使用一个可以在命令中调用的PHP脚本来完成这些任务——一个批处理过程。
春季批次
在askeet/batch/目录下创建一个名为load_data.php的文件,内容如下:
?服务器端编程语言(Professional Hypertext Preprocessor的缩写)
define(SF_ROOT_DIR),realpath(dirname(__FILE__)。/.));
定义( SF_APP , frontend );
define(SF_ENVIRONMENT , dev );
define(SF_DEBUG ,true);
需要一次(SF_ROOT_DIR。目录_分隔符。应用程序。目录_分隔符。SF_APP。目录_分隔符。“配置”。目录_分隔符。config . PHP’);
//初始化数据库管理器
$ database manager=new sfDatabaseManager();
$ database manager-initialize();
?
这个脚本什么都不做,或者几乎什么都不做:它定义一个路径、程序和要配置的环境,加载配置,并初始化数据管理器。但这已经很多了:这意味着下面编写的代码将通过使用自动加载的类自动连接到Propel对象和Symfony根类。
如果我们测试过Symfony的前端控制器(比如askeet/web/index.php),会发现这些代码非常熟悉。这是因为就像批处理请求一样,每个web请求都需要访问相同的对象和配置。
数据导入
既然批处理的框架已经准备好了,是时候让它有所成就了。需要完成的批次:
1读取YAML文件
2创建一个推进对象实例
3在链接数据库的数据表中创建相关记录。
这听起来很复杂,但是在Symfony中,由于sfPropelData对象,我们只需要两行代码就可以做到这一点。在askeet/batch/load_data.php脚本的最后?在之前添加以下代码:
data=new sfPropelData();
$ data-load data(SF config:get( SF _ data _ dir )。目录_分隔符。夹具’);
就是这样。创建一个sfPropelData对象,并通知它将指定目录(我们的fixtures目录)中的所有数据加载到databases.yml配置文件中定义的数据库中。
这里的DIRECTORY_SEPARATOR常量用于兼容Windows和*nix平台。
开始批处理
最后,我们可以检查这些代码是否值得我们讨论,并在命令行输入以下命令:
$ CD/home/SF projects/askeet/batch
$ php加载数据
通过刷新开发主页,我们可以在数据库中检测到这些修改:
http://askeet/frontend_dev.php
数据已成功加载。
默认情况下,sfPropelData对象会在加载新数据之前删除所有数据。我们还可以在当前数据后添加:
$ data=new sfPropelData();
$ data-setDeleteCurrentData(false);
$ data-load data(SF config:get( SF _ data _ dir )。目录_分隔符。夹具’);
访问模块中的数据
当请求问题模块的列表操作时,显示的页面是executeList()(可以在Askeet/Frontend/Modules/Question/Actions/action . class . PHP操作文件中找到)方法传递给Askeet/Apps/Frontend/Modules/Question/Templates/list success . PHP模板的结果。这是基于Symfony的控制器一章中解释的名称转换。让我们来看看执行的代码:
actions.class.php:
公共函数executeList()
{
$ this-questions=question peer:do select(new Criteria());
}
listSuccess.php:
.
?PHP foreach($ questions as $ question):
tr
td?php echo link_to($question- getId(), question/show?id=。$question- getId())?/td
td?php echo $question- getTitle()?/td
td?php echo $question- getBody()?/td
td?php echo $question- getCreatedAt()?/td
td?php echo $question- getUpdatedAt()?/td
/tr
?php endforeach?
一步一步,它的工作如下:
1该操作请求满足空条件的问题表的记录。
2记录列表放在一个数组($questions)中,并传递给模板
3模板在动作传递的问题中循环。
4模板显示记录中每一列的值。
在Symfony的proper-build-model命令调用中生成-getId()、- getTitle()、- getBody()和-getBody()等方法,用于获取Id、Title、body等数据字段的值。这些是标准的获取方法,通过在域名前添加一个get前缀来格式化,而Propel也提供了一个带有set前缀的标准设置方法。Propel文档描述了为每个类创建的访问方法。
我们可能困惑的是question peer:Do Select(new criteria())调用,这也是一个标准的Propel请求。Propel文档会详细解释。
如果上面写的代码我们没有完全理解和担心,过几天就清楚了。
修改问题/列表模板
现在,数据已经包含了对某个问题感兴趣的记录,因此应该很容易找到对某个问题感兴趣的用户。如果我们在askeet/lib/model/om/中查看Propel生成的BaseQuestion.php类,我们会注意到有一个-getInterests()方法。Propel会在兴趣数据表的定义中检测到question_id外键,从而推测会有一些对某个问题感兴趣的用户。这样通过修改listsuccess.php的模板(askeet/frontend/modules/question/templates/)就可以很容易的显示出我们想要的东西。在这个过程中,我们将删除那些难看的表格,并用我们漂亮的div格式替换它们:
?php use_helper(Text )?
h1常见问题/h1
?PHP foreach($ questions as $ question):
差异
差异
div id=interested_in_?php echo $question- getId()?
?PHP echo count($ question-getInterests())?
/div
/div
h2?PHP echo link _ to($ question-getTitle(), question/show?id=。$question- getId())?/h2
差异
?PHP echo truncate _ text($ question-getBody(),200)?
/div
/div
?php endforeach?
在这里,我们可以看到与原始listSuccess.php相同的foreach循环。link_to()和truncate_text()函数是Symfony提供的模板助手。第一个创建了同一个模块的另一个动作的超链接,而第二个将问题的内容缩短到200个字符。link_to()助手是自动加载的,但是如果我们想要使用truncate_Text(),我们必须声明使用该助手的文本组。
现在我们可以刷新main来测试我们的新模板:
http://askeet/frontend_dev.php/
感兴趣的用户数量显示在每个问题附近。要获得上面的显示,请下载main.css样式文件并将其放在askeet/web/css/目录中。
清楚的
proper-generate-crud命令将创建一些我们不需要的动作和模板。现在是时候摆脱他们了。
askeet/apps/frontend/modules/question/actions/actions . class . PHP中要移除的操作:
*执行索引
*执行编辑
*执行更新
*执行创建
*执行删除
askeet/apps/frontend/modules/question/templates/directory中要删除的模板:
editSuccess.php
明天见
今天是我们深入模型-视图-控制器世界的一大步:通过操作Propel对象模型的布局、模板、动作和对象,我们访问了一个MVC结构程序中的所有级别。如果我们不能完全理解这几层之间的桥梁,不要担心:它会一点一点变得更加清晰。
今天打开了很多文件。如果我们想知道一个项目中的文件是如何组织的,我们可以查看Symfony中关于文件结构的章节。
明天将是另一个伟大的日子:我们将修改视图,设置更复杂的路由规则,修改模块,深化数据操作和表之间的链接。