symfony24天教程之第3天 深入MVC架构

来源:互联网 发布:淘宝代理加盟可靠吗 编辑:程序博客网 时间:2024/04/30 00:39

 

Symfony回顾

在第2天你学习了怎样基于一个关系数据模块建立一个对象模块 ,并且为其中一个对象搭建一个框架。同时,在前几天中为应用创建的代码现在保存在SVN容器 http://svn.askeet.com里。

第3天的目标是为这个站点定义一个更好的布局,把问题列表定义为缺省页面,表示对1个问题感兴趣的用户人数,从一个文本文件读入数据到数据库用来作为测试数据。要做到事情并不多,但是要阅读并理解的东西还是蛮多的。

为了阅读这个教程你必须熟悉symfony 中的以下概念,项目,应用,模块,动作。这些概念在 symfony 宝典的 controller chapter章都有解释。

MVC模块

今天是第一天进入 MVC架构的世界,这到底意味着什么呢?简单的说,构成1个页面的代码被根据它们的类型分布在各种各样的文件里。

如果代码涉及到与具体页面无关的数据操作,那么它将被放到模块--Model(在这里是 askeet/lib/model/)。如果它涉及到最终的显示,那么它被放到显示--View;在symfony中,显示布局依赖于模板--template(例如 askeet/apps/frontend/modules/question/templates/)以及定义文件。最后,那些专注于连接上述代码到一起以把网站逻辑转变成老式的php的代码被放到控制器--Controller,在symfony中1个页面的Controller被称为动作--action(例如 askeet/apps/frontend/modules/question/actions).你可以在symfony宝典的 在symfony中实现MVC  章节中学到更多关于上述模块的知识。

虽然我们的应用显示只需要改变一点点,但是我们将要操作很多不同的文件。别晕,因为文件的组织以及把代码分离在不同的层中会将使他们更明白而且有用。

改变布局

在 装饰设计模式--decorator_desing_pattern中,那些被动作(action)调用的模板的内容被集中到一个全局的模板,或者布局中。换句话说,布局中包含了介面中所有不变的部分,它装饰(decorate)动作的结果。打开缺省布局(在 askeet/apps/frontend/templates/layout.php)并把它变成如下的样子。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
 
<?php echo include_http_metas() ?>
<?php echo include_metas() ?>
 
<?php echo include_title() ?>
 
<link rel="shortcut icon" href="/favicon.ico" />
 
</head>
<body>
 
  
<div id="header">
    
<ul>
      
<li><?php echo link_to('about', '@homepage'?></li>
    
</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 class="verticalalign"></div>
    
</div>
 
    
<div id="content_bar">
      
<!-- Nothing for the moment -->
      
<div class="verticalalign"></div>
    
</div>
  
</div>
 
</body>
</html>

我们尽量让修改更可读,所以我们把所有的样式都放到了css中,这里不解释这些css,因为css语法不是这个教程的目的,它们可以从这里 SVN repository下载,

我们创建了两个样式表(main.css和layout.css),把这两个文件复制到你的 askeet/web/css目录并且编辑你的 frontend/config/view.yml,改变自动载入的样式。

stylesheets:    [main, layout]

这个布局现在还很不够,以后它还会被重建(大约1周后)。在这个模板中重要的是在<head>的部分,包含了通常要包含的内容,还有就是 sf_content变量,里面是动作的结果。

再看看 我们做的这些修改的结果

http://askeet/frontend_dev.php/

应该可以看到下面的页面

关于环境

如果你为 http://askeet/frontend_dev.php/ 和 http://askeet/ 的不同而感到奇怪的话,你应该看看symfony宝典的 设定 章节。现在你只需要知道它们指向相同的应用但是不同的环境。1个环境使用1个唯一的设定,在设定里框架的某些特性可以根据需要被激活或者不激活。

在这里,/frontend_dev.php/指向开发环境,在这个环境的设定中,针对每次请求设定都被解析,HTML缓存被关闭,所有的调试工具都可用(包括一个在窗口右上角的半透明的工具条). 而 / URL --等同于 /index.php/ 窒指向产品环境,这里的设定被编译了,而且调试工具都不可用,这样可以提高发布页面的速度。

这两个PHP脚本-- frontend_dev.php和 index.php --被称为 前台控制器--front controllers。所有的对应用的请求由它们处理。你可以在 askeet/web/目录下找到这两两个文件。 事实上,index.php应该被命名为frontend_prod.php,但是因为frontend 是你创建的第一个应用,symfony推论你可能想把它作为你的缺省应用所以把它起名为 index.php.这样你就可以在产品环境里直接输入 /来看到你的应用。 如果你想学写更多关于前台控制器以及MVC模式控制器层的知识,请看symfony宝典的 控制器 章节。
一个好的做法是现在开发环境中调试直至所有需要的特性都被满足然后切换到产品环境来检查速度以及"友善的"URL.

记得如果你加入了一些类或者你改变了定义文件,然后想看看结果,一定要先清除缓存。

重新定义缺省页面

现在,如果你访问这个网站的首页,你会看到 Congratulation 页面。一个更好的主意是当访问网站的时候表示出问题的列表(在这里就是 question/list ,也就是 question模块的list 动作)。为了达到这个目的,打开前台应用的路由定义文件,askeet/apps/frontend/config/routing.yml找到homepage这一节,修改为

homepage:
  url:   /
  param: { module: question, action: list }

在开发环境刷新首页(http://askeet/frontend_dev.php) 现在它表示的应该是问题的列表了。

如果你是一个好奇心很重的人,你会想找那个包含了'Congratulations'消息的页面,但是你会很奇怪你的askeet页面里并不能找到这个页面。事实上,default/index动作的模板定义在symfony data目录,而且是与项目无关的。如果你想重载它,你可以为你的项目定义一个default模块。

关于路由系统的左右将在不久的将来讲到,但是如果你感兴趣的话,你可以看一看 symfony宝典的 路由系统 章节。

定义测试数据

现在首页表示的列表应该是空的,当然除非你加入了你自己的问题。当你开发你一个应用的时候,一个好的想法是有一些数据供你来处理。手工输入测试数据(或者通过 CRUD介面 或者直接输入到数据库)是在是很痛苦,所以symfony可以使用text文件来发布数据到数据库里。

我们要创建一个文本数据文件,把它放在askeet/data/fixtures/目录下(先要创建这个目录)。创建一个叫做test_data.yml的目录,放入如下内容。

 

User:
  anonymous:
    nickname:   anonymous
    first_name: Anonymous
    last_name:  Coward

  fabien:
    nickname:   fabpot
    first_name: Fabien
    last_name:  Potencier

  francois:
    nickname:   francoisz
    first_name: François
    last_name:  Zaninotto

Question:
  q1:
    title: What shall I do tonight with my girlfriend?
    user_id: fabien
    body:  |
      We shall meet in front of the Dunkin'Donuts before dinner
, 
      and I haven't the slightest idea of what I can do with her. 
      She's not interested in programming
, space opera movies nor insects.
      She's kinda cute
, so I really need to find something 
      that will keep her to my side for another evening.

  q2:
    title: What can I offer to my step mother?
    user_id: anonymous
    body:  |
      My stepmother has everything a stepmother is usually offered
      (watch
, vacuum cleaner, earrings, del.icio.us account). 
      Her birthday comes next week
, I am broke, and I know that 
      if I don't offer her something sweet
, my girlfriend 
      won't look at me in the eyes for another month.

  q3:
    title: How can I generate traffic to my blog?
    user_id: francois
    body:  |
      I have a very swell blog that talks 
      about my class and mates and pets and favorite movies.

Interest:
  i1: { user_id: fabien
, question_id: q1 }
  i2: { user_id: francois
, question_id: q1 }
  i3: { user_id: francois
, question_id: q2 }
  i4: { user_id: fabien
, question_id: q2 }

首先你必须了解YAML,如果你不熟悉YAML的话,你可能不知道YAML格式是框架定义文件的格式。它一点也不高级--如果你接触过XML或者.ini文件,要在其中加入一个定义句柄给symfony读取很简单。如果你有空而且有耐心,你可以看一看 symfony宝典的  实践中的定义 章节 ,来了解YAML和symfony定义。在这里,如果你不熟悉YAML语法,你可以从 这里 开始,因为这个教程有很多地方会用到它。

好的,回到测试数据文件,它定义对象的实例,标记了内部名称。这个标签将用来链接相关的对象而不用定义id(通常是自增而且不可编辑的).例如,第一个创建的对象是 User 类,被标记成 fabien.第一个Question被标记成q1,这两个标记被用来创建一个Interest类。

Interest:
  i1:
    user_id: fabien
    question_id: q1

前面给出的数据文件用YAML语法描述了上面讲的这些东西,你可以在 symfony宝典的 数据文件 章节找到更多关于数据发布文件的知识。

注意:这里不需要定义created_at和updated_at的值,因为symfony会自动加入缺省值。

创建一个批处理来导入到数据库

 下一步是把数据导入到数据库,我们将通过一个可以命令行执行的PHP脚本来实现它。

批处理的基干在askeet/batch/ 目录下创建一个叫做 load_data.php的文件,内容如下。

 

<?php
 
define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_APP',         'frontend');
define('SF_ENVIRONMENT', 'dev');
define('SF_DEBUG',       true);
 
require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');
 
// initialize database manager
$databaseManager = new sfDatabaseManager();
$databaseManager->initialize();
 
?>

这个脚本没做什么,或者说几乎什么也没做:它定义一个路径,一个应用,一个环境用以取得定义,载入这个定义,以及初始化数据库管理器。但是这已经够多了,这意味着下面写的代码将利用自动载入的类,自动连接到Propel对象,以及symfony根类。

注意:如果你研究过symfony的前台控制器(例如 askeet/web/index.php)。你会发现代码实在是很相像。那是因为每个web请求都需要访问相同的对象和定义,这一点和批处理是一样的。

数据导入

现在批处理的框架已经准备好。是时候开始做些什么了。这个批处理要做下面的事情:

1,读取YAML文件

2,创建一个Propel对象的实例

3,在连接好的数据库的表里创建相关的记录。

听上去很复杂,但是在symfony里,你只需要2行代码就可以完成它们,这要感谢sfPropelData对象。只要加入以下的代码在 askeet/batch/load_data.php 的最后( ?> 之前)就行了。

$data = new sfPropelData();
$data->loadData(sfConfig::get('sf_data_dir').DIRECTORY_SEPARATOR.'fixtures');

对,这就是全部。一个sfPropelData对象被创建了,它被要求把所有指定目录--我们创建的fixtures目录--里的数据到在databases.yml中定义的数据库里。

注意: DIRECTORY_SEPARATOR常数被用来兼容Windows和*nix平台的目录分割符。

执行批处理

最后,我们可以看看这几行代码是否有用,在命令行里输入、

$ cd /home/sfprojects/askeet/batch
$ php load_data.php

通过更新开发环境首页看看数据库是否有变化

http://askeet/frontend_dev.php

嘢!数据有了!

注意:缺省的 sfPropelData对象会在载入数据前删除所有的现有数据。你也可以把数据添加在现有数据之上。

$data = new sfPropelData();
$data->setDeleteCurrentData(false);
$data->loadData(sfConfig::get('sf_data_dir').DIRECTORY_SEPARATOR.'fixtures');

 

在模块中访问数据

当我们访问question模块的list动作时,显示的页面是executeList()方法(在 askeet/apps/frontend/modules/question/actions/action.class.php 这个动作文件)通过 askeet/apps/frontend/modules/question/templates/listSuccess.php模板表示的结果。这些是基于一个在 symfony宝典的控制器 章节里解释的命名规则的。让我们看看执行的代码。

actions.class.php:

public function executeList ()
{
  
$this->questions = QuestionPeer::doSelect(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,动作请求Question表达所有满足空条件的数据--也就是说所有数据

2,这个数据的列表被放到一个数组($questions),它将被交给模板处理

3,模板遍历动作交来的所有的question

4,模板表示每条记录的各个列的值

这些 ->getID(),->getTitle(),->getBody()等方法在 symfony propel-build-model命令行执行的时候被创建(这是昨天的教程内容).它们被用来取得id,title,body等字段的值。这是些标准的取值函数,格式是在驼峰命名法则的字段名前加上前缀get。Propel还提供标准赋值函数,前缀为set.。 Propel文档 阐述了为每个类的创建的存取器。

至于神秘的QuestionPeer::doSelect(new Criteria()) 调用,也是一个标准的Propel请求。Propel文档十分彻底地解释了它。

如果你不明白上面的代码也没关系,你会在接下来几天内弄懂它们。

修改questin/list模板

现在数据库中包含了对question感兴趣的人,要取得对一个question感兴趣的用户的数目,如果你看看声称在askeet/lib/model/om/目录的 BaseQuestion.php类,你会注意到一个 ->getInterests()方法。Propel看到在Interest表的定义里有一个外键 question_id,所以推断一个question有多个interest.这让我们很容易修改askeet/apps/frontend/modules/question/templates/listSuccess.php模板来表示我们想要的数字--对一个问题感兴趣的用户次数。 这次,我们去掉那些难看的表格,用友好的 div来代替它们。

<?php use_helper('Text'?>
 
<h1>popular questions</h1> 
 
<?php foreach($questions as $question): ?>
  
<div class="question">
    
<div class="interested_block">
      
<div class="interested_mark" 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>
 
    
<div class="question_body">
      
<?php echo truncate_text($question->getBody(), 200?>
    
</div>
  
</div>
<?php endforeach?>

 你会看到这里有和原来的listSuccess.php同样的foreach循环。link_to()和truncate_text()函数是symfony提供的模板助手。第一个是创建一个去同一个模块的不同动作的超链接。第二个会截取question的内容的前200个字符。 link_to()助手是自动加载的,但是你必须申明对 Text 群组的使用才能用 truncate_text().

来吧,再刷新下开发环境首页看看我们的新模板效果。

http://askeet/frontend_dev.php/

对一个question感兴趣的用户数被正确地表示在每个问题旁边。要得到上述章节的结果,请下载 main.css保存到你的askeet/web/css 目录。

清理
命令 propel-generate-crud 创建了一些动作和模板不需要用到,现在可以清除它们

askeet/apps/frontend/modules/question/actions/actions.class.php 要清楚的动作有

executeIndex

executeEdit

executeUpdate

executeCreate

executeDelete

在 askeet/apps/frontend/modules/question/templates/中要清楚的模板有

editSuccess.php

明天见

今天进行了MVC范例中的重要一步:通过操作Propel对象模块的层,模板,动作以及对象,你接触到了一个MVC结构应用的所有的层,不用为你不明白这些层之间的联系:它会变得越来越清楚。

今天打开了好几个文件,如果你想了解这些文件是怎样被阻止到一个项目中的,请参考 symfony宝典的 文件结构 章节。

明天将是另一个总要的日子:我们将修改 显示,建立一个更复杂的路由政策,修改模块,以及更盛入数据操作和表之间的连接。

在此之前,请好好休息,还可以从以下链接访问今天的教程源代码(标记 release_day3)

http://svn.askeet.com/tags/release_day_3

问题和反馈

如果你发现一个拼写错误或其他错误,请 打开 这里

如果你需要支持或者有一个技术问题,请在 用户邮件列表 或 讨论 发帖

原创粉丝点击