Development

Documentation/zh_CN/jobeet/1.2/04 (diff)

You must first sign up to be able to contribute.

Changes from Version 1 of Documentation/zh_CN/jobeet/1.2/04

Show
Ignore:
Author:
sinosmond (IP: 123.120.187.120)
Timestamp:
04/17/09 09:48:26 (9 years ago)
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Documentation/zh_CN/jobeet/1.2/04

    v0 v1  
     1{{{ 
     2#!WikiMarkdown 
     3 
     4第 4 天: 控制器和视图 
     5================================== 
     6 
     7昨天,我们研究了 symfony 是如何通过抽象化数据库引擎间的差别, 
     8将关联成分转化为面向对象的类,来简化数据库管理的。 
     9我们使用 ##ORM## 描述数据库结构、生成数据表,并给数据库添加了一些初始数据。 
     10 
     11今天,我们将对昨天生成的基本的 `job` 模块做进一步的定制。 `job` 模块已经 
     12包含了 Jobeet 所需的所有代码: 
     13 
     14 * 显示所有工作列表的页面 
     15 * 创建新工作的页面 
     16 * 更新已存在工作的页面 
     17 * 删除工作的页面 
     18 
     19虽然代码已经有了,但是我们还要修改模板,让它更符合 Jobeet 网页的设计图。 
     20 
     21~MVC~ 体系结构 
     22---------------------- 
     23 
     24如果你曾经开发 PHP 的网站时没有用过框架,你可能习惯于将 PHP 代码与 HTML  
     25代码混编在同一个 PHP 文件中。 
     26这些 PHP 文件可能会包含类似的结构:初始化和全局配置、与请求页面相关的业务逻辑、 
     27数据库中记录的获取以及最终建立页面的 HTML 代码。 
     28 
     29你可能使用过模板引擎从 HTML 代码中分离出业务逻辑, 
     30也可能使用过数据抽象层从业务逻辑中分离出关系模型。 
     31但是大多时候,这只会让你产生大量的难于管理的代码。 
     32虽然开发速度提高了,但不久,你就发现代码越来越难以维护, 
     33尤其是除了你之外没有人能读懂这些代码是如何工作的。 
     34 
     35针对上述种种问题,有一个非常好的解决办法。对 Web 开发而言, 
     36现在最普遍使用的组织代码的方式就是 [**MVC 设计模式**](http://en.wikipedia.org/wiki/Model-view-controller)。 
     37简单来说,MVC 设计模式定义了一种最自然的组织代码的方式。 
     38这种模式将代码分离为 **3个层次**: 
     39 
     40 * **~模型~**层定义业务逻辑(数据库属于这个层)。从昨天的内容你已经知道, 
     41   symfony 将模型的所有类和与其相关的文件都存放在 `lib/model/` 目录下。 
     42 * **~视图~**是与用户交互的层(模板引擎是这个层的一部分)。在 symfony 中, 
     43   视图层主要由 PHP 模板组成。这些文件存储在不同的 `templates/` 目录中, 
     44   今天稍后会讲到。 
     45 * **~控制器~**是一段代码,它从模型层读取数据传送给视图层,由视图层显示到客户端。 
     46   在第1天安装 symfony 时,我们看到所有的请求都由前端控制器(index.php 和 frontend_dev.php) 
     47   管理。这些前端控制器(front controllers)将实际工作交由**动作**(actions)处理。 
     48   象我们昨天看到的一样,这些动作被合理的组织到**模块**(modules)中。 
     49 
     50![MVC](http://www.symfony-project.org/images/jobeet/1_2/04/mvc.png) 
     51 
     52今天,我将按第2天给出的布局修改主页和工作页,并让它们能动态显示内容。 
     53这样的话,我们要在许多文件上做很多事,你可以从其中了解 symfony 目录结构, 
     54及各层代码分离的方法。 
     55 
     56 
     57布局(Layout) 
     58---------- 
     59 
     60首先,如果你仔细研究了网页的设计图,你会发现每个页面都有很多相同的地方。 
     61这表示我们必须在每个页面添加一些相同的代码。如你所知,代码重复是非常糟糕的, 
     62无论是我们谈及的 HTML 还是 PHP 代码,所以我们需要找到一种避免这些公共的视图 
     63元素代码重复的方法。 
     64 
     65解决这个问题的一种办法是:将重复代码(如头部代码和底部代码)分别写入单独的页面, 
     66然后再让每个模板去调用这些单独的页面。下面是个简单的图示: 
     67 
     68![Header and footer](http://www.symfony-project.org/images/jobeet/1_2/04/header_footer.png) 
     69 
     70这个办法存在一个缺陷,就是头部和底部两个独立的文件中会含有没有闭合的 HTML 标签。 
     71为了让一切看上去很完美,我们使用了另外一种更好的设计模式来解决这个问题:[~装饰~ 设计模式](http://en.wikipedia.org/wiki/Decorator_pattern)。 
     72装饰设计模式(Decorator design pattern)以另外的方式解决这个问题: 
     73就是使用全局模板(在 symfony 中,称为 **~layout~** ), 
     74来装饰展示内容的模板(译注:内容模板如同画,全局模板如同框,装饰模式就是用画框来装饰画。 
     75反过来说,一个画框又可以装饰不同的画): 
     76 
     77![Layout](http://www.symfony-project.org/images/jobeet/1_2/04/layout.png) 
     78 
     79一个应用程序的默认全局模板(layout)是 `layout.php` , 
     80可以在 `apps/frontend/templates/` 目录下找到它。 
     81这个目录中存放的全局模板文件可以被应用程序中的每个模块(module)使用。 
     82 
     83用下面的代码替换 symfony 默认的 layout.php: 
     84 
     85    [php] 
     86    <!-- apps/frontend/templates/layout.php --> 
     87    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
     88     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
     89    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 
     90      <head> 
     91        <title>Jobeet - Your best job board</title> 
     92        <link rel="shortcut icon" href="/favicon.ico" /> 
     93        <?php include_javascripts() ?> 
     94        <?php include_stylesheets() ?> 
     95      </head> 
     96      <body> 
     97        <div id="container"> 
     98          <div id="header"> 
     99            <div class="content"> 
     100              <h1><a href="/job"> 
     101                <img src="/images/logo.jpg" alt="Jobeet Job Board" /> 
     102              </a></h1> 
     103 
     104              <div id="sub_header"> 
     105                <div class="post"> 
     106                  <h2>Ask for people</h2> 
     107                  <div> 
     108                    <a href="/job/new">Post a Job</a> 
     109                  </div> 
     110                </div> 
     111 
     112                <div class="search"> 
     113                  <h2>Ask for a job</h2> 
     114                  <form action="" method="get"> 
     115                    <input type="text" name="keywords" 
     116                      id="search_keywords" /> 
     117                    <input type="submit" value="search" /> 
     118                    <div class="help"> 
     119                      Enter some keywords (city, country, position, ...) 
     120                    </div> 
     121                  </form> 
     122                </div> 
     123              </div> 
     124            </div> 
     125          </div> 
     126 
     127          <div id="content"> 
     128            <?php if ($sf_user->hasFlash('notice')): ?> 
     129              <div class="flash_notice"> 
     130                <?php echo $sf_user->getFlash('notice') ?> 
     131              </div> 
     132            <?php endif; ?> 
     133 
     134            <?php if ($sf_user->hasFlash('error')): ?> 
     135              <div class="flash_error"> 
     136                <?php echo $sf_user->getFlash('error') ?> 
     137              </div> 
     138            <?php endif; ?> 
     139 
     140            <div class="content"> 
     141              <?php echo $sf_content ?> 
     142            </div> 
     143          </div> 
     144 
     145          <div id="footer"> 
     146            <div class="content"> 
     147              <span class="symfony"> 
     148                <img src="/images/jobeet-mini.png" /> 
     149                powered by <a href="http://www.symfony-project.org/"> 
     150                <img src="/images/symfony.gif" alt="symfony framework" /> 
     151                </a> 
     152              </span> 
     153              <ul> 
     154                <li><a href="">About Jobeet</a></li> 
     155                <li class="feed"><a href="">Full feed</a></li> 
     156                <li><a href="">Jobeet API</a></li> 
     157                <li class="last"><a href="">Affiliates</a></li> 
     158              </ul> 
     159            </div> 
     160          </div> 
     161        </div> 
     162      </body> 
     163    </html> 
     164 
     165与其它的一些模板程序不大相同,symfony 的~模板~都是纯 PHP 文件。 
     166在 layout 模板中包含一些 PHP 变量和函数调用。 
     167如 `~$sf_content~` 就是一个非常有趣的变量:它由框架定义, 
     168包含由动作(action)生成的 HTML 代码。 
     169 
     170浏览 `job` 模块(http://jobeet.localhost/frontend_dev.php/job), 
     171可以看到所有被 layout 装饰的动作。 
     172 
     173样式、图片和 javascript 
     174---------------------------------------- 
     175 
     176因为这个教程不涉及 Web 设计的内容,所以我们已经为 Jobeet 准备好了所有要使用到的 
     177资产(asset)文件: 
     178[下载图片文件](http://www.symfony-project.org/get/jobeet/images.zip) 
     179包然后将其解压缩到 `web/images/` 目录; 
     180[下载样式文件](http://www.symfony-project.org/get/jobeet/css.zip) 
     181包然后将其解压缩到 `web/css/` 目录。 
     182 
     183>**NOTE** 
     184>在布局中,还包含了一个 *favicon*。你可以 
     185>[下载 Jobeet 的 favicon](http://www.symfony-project.org/get/jobeet/favicon.ico) 
     186>然后将其放到 `web/` 目录。 
     187 
     188![The job module with a layout and assets](http://www.symfony-project.org/images/jobeet/1_2/04/job_layout_assets.png) 
     189 
     190>**TIP** 
     191>默认的情况下,`generate:project` 任务会自动在 `web/` 目录下生成三个子目录用来 
     192>分别存放项目的不同资产文件: `web/images/` 目录用来保存图片, 
     193>`web/~css~/` 保存 css ~样式表~,`web/js/` 保存 ~JavaScript~ 文件。 
     194>这是 symfony 定义的众多~约定(convention)~之一,如果没有特别的声明, 
     195>symfony 会自动到这些目录寻找相应类型的文件。 
     196>当然,你也可以通过显示的说明,将这些类型文件放到 `web/` 下的任意目录中。 
     197 
     198精明的读者会注意到,虽然在默认的布局文件中并没有提及 `main.css` 文件, 
     199但是他却明确地出现在所生成的 HTML 中。而且除了这个样式文件的引用之外并无其他。 
     200这是怎么回事呢? 
     201 
     202样式文件已经在 `<head>` 标签之内的 `~include_stylesheets~()` 函数调用了。 
     203`include_stylesheets()` 函数是一个**辅助函数(helper)**。 
     204辅助函数是 symfony 中内置的函数,它可以传送参数并生成 HTML 代码。 
     205很多情况下辅助函数对模板中频繁使用的代码片段打包并节约书写 HTML 代码的时间。 
     206`include_stylesheets()` 辅助函数用来生成一个调用 css 文件的  `<link>` 标签。 
     207 
     208但是辅助函数是如何知道调用哪个样式文件呢? 
     209 
     210~视图~ 层可以通过编辑应用程序的 `~view.yml~` 配置文件加以配置。 
     211下面是 `generate:app` 任务自动生成的 `~view.yml~` 文件: 
     212 
     213    [yml] 
     214    # apps/frontend/config/view.yml 
     215    default: 
     216      http_metas: 
     217        content-type: text/html 
     218 
     219      metas: 
     220        #title:        symfony project 
     221        #description:  symfony project 
     222        #keywords:     symfony, project 
     223        #language:     en 
     224        #robots:       index, follow 
     225 
     226      stylesheets:    [main.css] 
     227 
     228      javascripts:    [] 
     229 
     230      has_layout:     on 
     231      layout:         layout 
     232 
     233这个 `view.yml` 文件为应用程序下的所有模板设置默认配置。 
     234例如 `stylesheets` 项定义了应用程序每个页面要包含的样式文件的一个数组 
     235(这个包含由 `include_stylesheets()` 辅助函数完成)。 
     236 
     237>**NOTE** 
     238>在默认的 `view.yml` 配置文件中,被引用的文件是 `main.css` 而不是 `/css/main.css`。 
     239>实际上,两者是等价的,因为对于样式文件而言 symfony 的路径前缀是相对于 `/~css~/` 的。 
     240 
     241当定义多个样式时,symfony 会按照数组中文件名的顺序,依次包含每个样式文件: 
     242 
     243    [yml] 
     244    stylesheets:    [main.css, jobs.css, job.css] 
     245 
     246你可以改变它们的 `media` 属性,也可以省略样式文件名的 `.css` 后缀: 
     247 
     248    [yml] 
     249    stylesheets:    [main.css, jobs.css, job.css, print: { media: print }] 
     250 
     251这个配置将处理为如下的代码: 
     252 
     253    [php] 
     254    <link rel="stylesheet" type="text/css" media="screen" 
     255      href="/css/main.css" /> 
     256    <link rel="stylesheet" type="text/css" media="screen" 
     257      href="/css/jobs.css" /> 
     258    <link rel="stylesheet" type="text/css" media="screen" 
     259      href="/css/job.css" /> 
     260    <link rel="stylesheet" type="text/css" media="print" 
     261      href="/css/print.css" /> 
     262 
     263>**TIP** 
     264>`view.yml` 配置文件也定义应用程序使用的 ~layout~。 
     265>默认为 `layout` 模板,因此 symfony 使用 `layout.php` 文件装饰每个页面。  
     266>如果将 `~has_layout~` 项设置为 `false`,装饰过程将被禁用。 
     267 
     268It works as is but the `jobs.css` file is only needed for the homepage and the 
     269`job.css` file is only needed for the job page.  
     270`view.yml` 配置文件能以每模块为基础进行定制。  
     271将应用程序的 `view.yml` 文件改回只包含 `main.css` 文件: 
     272 
     273    [yml] 
     274    # apps/frontend/config/view.yml 
     275    stylesheets:    [main.css] 
     276 
     277为 `job` 模块定制视图,在 `apps/frontend/modules/job/config/` 目录下创建 `view.yml` 文件: 
     278 
     279    [yml] 
     280    # apps/frontend/modules/job/config/view.yml 
     281    indexSuccess: 
     282      stylesheets: [jobs.css] 
     283 
     284    showSuccess: 
     285      stylesheets: [job.css] 
     286 
     287在 `indexSuccess` 和 `showSuccess` 部分之下(他们是与 `index` 和 `show`  
     288动作相联系的模板名称,关于模板的命名规则后面会详细讲述), 
     289你能重新定制任何应用程序的 `view.yml` 配置文件的 `default` 部分之下的任何配置条目。 
     290所有定制的配置条目会与应用程序的 `view.yml` 中的配置条目合并在一起。 
     291(合并的原则是:模块中所有的配置条目及模块中没有而应用程序中有的配置条目将全部保留; 
     292应用程序与模块中相同的配置条目将被模块中的配置条目覆盖。) 
     293你也可以用特别的 `all` 部分为一个模块的所有动作定义某些配置。 
     294 
     295 
     296>**SIDEBAR** 
     297>symfony 的配置原则 
     298> 
     299>在众多的 symfony ~配置~文件中, 相同的设置可以在不同的级别中定义: 
     300> 
     301>  * 默认配置(default configuration)位于 symfony 框架中 
     302>  * 项目的全局配置(global configuration)位于 `config/` 目录下 
     303>  * 应用程序的局部配置(local configuration)位于 `apps/APP/config/` 目录下 
     304>  * 模块的局部配置位于 `apps/APP/modules/MODULE/config/` 目录下 
     305> 
     306>为获得良好的性能,程序运行时,配置系统将合并这些存在于不同文件中的设置, 
     307>并生成一个缓存文件。这就是为什么改变设置时需要清空缓存的原因。 
     308 
     309一般来说,配置文件中的每个设置都可以使用 PHP 代码的动态实现。 
     310例如,你可以在模板中直接使用 `~use_stylesheet~()` 辅助函数包含一个样式表来 
     311替换 `job` 模块的 `view.yml` 中的设置: 
     312 
     313    [php] 
     314    <?php use_stylesheet('main.css') ?> 
     315 
     316你也可以在全局模板中使用这个辅助函数实现全局样式表的包含。 
     317 
     318选择哪种方法只是一个使用偏好问题。一方面,`view.yml` 提供了一种为模块里所有动作设置配置的方法, 
     319这在一个模板里是不可能实现的(模板中只能配置使用这个模板的动作),但这种配置是完全静态的。 
     320另一方面,使用 `use_stylesheet()` ~辅助函数~ 会非常灵活,另外使用辅助函数会使样式表定义和 HTML 
     321代码放在一起,修改起来会方便许多。 
     322 
     323在 Jobeet 中,我们将使用 `use_stylesheet()` 辅助函数的方式,所以你可以删除 `view.yml` 文件。 
     324我们仅仅创建并更新使用 `use_stylesheet()` 调用的 `job` 模板: 
     325 
     326    [php] 
     327    <!-- apps/frontend/modules/job/templates/indexSuccess.php --> 
     328    <?php use_stylesheet('jobs.css') ?> 
     329 
     330    <!-- apps/frontend/modules/job/templates/showSuccess.php --> 
     331    <?php use_stylesheet('job.css') ?> 
     332 
     333>**NOTE** 
     334>类似地, JavaScript 配置可以通过设置 `view.yml` 配置文件中的 `javascripts` 配置条目完成, 
     335>也可以通过在模板使用 `~use_javascript~()` 辅助函数来定义 JavaScript 文件的包含。 
     336 
     337Job 的首页 
     338---------------- 
     339 
     340正如第3天看到的,job首页已经通过 `job` 模块中 `index` 动作生成。 
     341`index` 动作是首页的控制器部分,关联的模板 `indexSuccess.php` 是视图部分: 
     342 
     343    apps/ 
     344      frontend/ 
     345        modules/ 
     346          job/ 
     347            actions/ 
     348              actions.class.php 
     349            templates/ 
     350              indexSuccess.php 
     351 
     352### 动作 
     353 
     354每个 ~action~ 都表现为一个类的方法。 对于 job 首页而言, 类是 `jobActions`  
     355(命名方式:模块名+`Action`后缀),方法是 `executeIndex()` 
     356(命名方式:`execute`+以驼峰式书写的动作名)。 
     357这个动作从数据库中获取全部的工作记录: 
     358 
     359    [php] 
     360    // apps/frontend/modules/job/actions/actions.class.php 
     361    class jobActions extends sfActions 
     362    { 
     363      public function executeIndex(sfWebRequest $request) 
     364      { 
     365<propel> 
     366        $this->jobeet_job_list = JobeetJobPeer::doSelect(new Criteria()); 
     367</propel> 
     368<doctrine> 
     369        $this->jobeet_job_list = Doctrine::getTable('JobeetJob') 
     370          ->createQuery('a') 
     371          ->execute(); 
     372</doctrine> 
     373      } 
     374 
     375      // ... 
     376    } 
     377 
     378<propel> 
     379让我们仔细看一下这段代码: `executeIndex()` 方法(控制器)调用模型的 `JobeetJobPeer`  
     380方法获取所有工作记录(`new Criteria()`),这个方法返回一个包含所有 `JobeetJob` 对象 
     381【symfony 的 ORM 将数据表中每条记录转化成一个对象】的数组,并将这个数组赋值给 
     382 `jobeet_job_list` 对象属性。 
     383</propel> 
     384<doctrine> 
     385让我们仔细看一下这段代码: `executeIndex()` 方法(控制器)调用表 `JobeetJob` 生成一个查询 
     386获取所有工作的记录。返回 `JobeetJob` 对象的 `Doctrine_Collection` ,并将其赋值给 
     387 `jobeet_job_list` 对象属性。 
     388</doctrine> 
     389 
     390所有这些对象属性(jobeet_job_list)都会自动传递给模板(视图)。 
     391要将数据从控制器传送到视图【或者说从动作传给模板】,只需要定义一个新属性: 
     392 
     393    [php] 
     394    public function executeFooBar(sfWebRequest $request) 
     395    { 
     396      $this->foo = 'bar'; 
     397      $this->bar = array('bar', 'baz'); 
     398    } 
     399 
     400你可以直接在模板中用 `$foo` 和 `$bar` 变量访问这个两个属性。 
     401 
     402### 模板 
     403 
     404默认情况下,~模板~ 名称与一个动作的名称是相联系的(模板的命名规则:动作名+`Success`后缀)。 
     405 
     406`indexSuccess.php` 模板为所有工作生成 HTML 表格用来展示工作信息,下面是当前模板的代码: 
     407 
     408    [php] 
     409    <!-- apps/frontend/modules/job/templates/indexSuccess.php --> 
     410    <?php use_stylesheet('jobs.css') ?> 
     411 
     412    <h1>Job List</h1> 
     413 
     414    <table> 
     415      <thead> 
     416        <tr> 
     417          <th>Id</th> 
     418          <th>Category</th> 
     419          <th>Type</th> 
     420    <!-- more columns here --> 
     421          <th>Created at</th> 
     422          <th>Updated at</th> 
     423        </tr> 
     424      </thead> 
     425      <tbody> 
     426        <?php foreach ($jobeet_job_list as $jobeet_job): ?> 
     427        <tr> 
     428          <td> 
     429            <a href="<?php echo url_for('job/show?id='.$jobeet_job->getId()) ?>"> 
     430              <?php echo $jobeet_job->getId() ?> 
     431            </a> 
     432          </td> 
     433          <td><?php echo $jobeet_job->getCategoryId() ?></td> 
     434          <td><?php echo $jobeet_job->getType() ?></td> 
     435    <!-- more columns here --> 
     436          <td><?php echo $jobeet_job->getCreatedAt() ?></td> 
     437          <td><?php echo $jobeet_job->getUpdatedAt() ?></td> 
     438        </tr> 
     439        <?php endforeach; ?> 
     440      </tbody> 
     441    </table> 
     442 
     443    <a href="<?php echo url_for('job/new') ?>">New</a> 
     444 
     445在模板代码中, `foreach` 遍历所有 `Job` 对象(`$jobeet_job_list`),并输出数据表中 
     446每条工作记录中每个字段(column)的值。 
     447记住,我们可以通过调用存取器、很容易的访问记录中的每个字段,存取器有统一的命名规则: 
     448是以 `get`+驼峰式书写的字段名(例如 `getCreatedAt()` 方法可以获取一条记录的 
     449 `created_at` 字段的值)。 
     450 
     451我们修改一下模板,只显示需要的内容: 
     452 
     453    [php] 
     454    <!-- apps/frontend/modules/job/templates/indexSuccess.php --> 
     455    <?php use_stylesheet('jobs.css') ?> 
     456 
     457    <div id="jobs"> 
     458      <table class="jobs"> 
     459        <?php foreach ($jobeet_job_list as $i => $job): ?> 
     460          <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>"> 
     461            <td class="location"><?php echo $job->getLocation() ?></td> 
     462            <td class="position"> 
     463              <a href="<?php echo url_for('job/show?id='.$job->getId()) ?>"> 
     464                <?php echo $job->getPosition() ?> 
     465              </a> 
     466            </td> 
     467            <td class="company"><?php echo $job->getCompany() ?></td> 
     468          </tr> 
     469        <?php endforeach; ?> 
     470      </table> 
     471    </div> 
     472 
     473![Homepage](http://www.symfony-project.org/images/jobeet/1_2/04/homepage.png) 
     474 
     475模板中的 `url_for()` 也是一个辅助函数,我们明天再说它。 
     476 
     477Job 页面的模板 
     478--------------------- 
     479 
     480现在我们定制 job 页的模板,打开 `showSuccess.php` 文件,用下面代码替换现有的内容: 
     481 
     482    [php] 
     483    <!-- apps/frontend/modules/job/templates/showSuccess.php --> 
     484    <?php use_stylesheet('job.css') ?> 
     485    <?php use_helper('Text') ?> 
     486 
     487    <div id="job"> 
     488      <h1><?php echo $job->getCompany() ?></h1> 
     489      <h2><?php echo $job->getLocation() ?></h2> 
     490      <h3> 
     491        <?php echo $job->getPosition() ?> 
     492        <small> - <?php echo $job->getType() ?></small> 
     493      </h3> 
     494 
     495      <?php if ($job->getLogo()): ?> 
     496        <div class="logo"> 
     497          <a href="<?php echo $job->getUrl() ?>"> 
     498            <img src="/uploads/jobs/<?php echo $job->getLogo() ?>" 
     499              alt="<?php echo $job->getCompany() ?> logo" /> 
     500          </a> 
     501        </div> 
     502      <?php endif; ?> 
     503 
     504      <div class="description"> 
     505        <?php echo simple_format_text($job->getDescription()) ?> 
     506      </div> 
     507 
     508      <h4>How to apply?</h4> 
     509 
     510      <p class="how_to_apply"><?php echo $job->getHowToApply() ?></p> 
     511 
     512      <div class="meta"> 
     513<propel> 
     514        <small>posted on <?php echo $job->getCreatedAt('m/d/Y') ?></small> 
     515</propel> 
     516<doctrine> 
     517        <small>posted on <?php echo date('m/d/Y', strtotime($job->getCreatedAt())) ?></small> 
     518</doctrine> 
     519      </div> 
     520 
     521      <div style="padding: 20px 0"> 
     522        <a href="<?php echo url_for('job/edit?id='.$job->getId()) ?>"> 
     523          Edit 
     524        </a> 
     525      </div> 
     526    </div> 
     527 
     528这个模板直接在动作中使用 `$job` 变量显示工作信息。我们前边提到过,这个变量是在动作里定义的一个对象属性。 
     529因为我们将 `$jobeet_job` 改成了 `$job`,所以要相应的修改 `show` 动作里的对象属性(注意,有两个变量要修改): 
     530 
     531    [php] 
     532    // apps/frontend/modules/job/actions/actions.class.php 
     533    public function executeShow(sfWebRequest $request) 
     534    { 
     535<propel> 
     536      $this->job = 
     537       ➥ JobeetJobPeer::retrieveByPk($request->getParameter('id')); 
     538</propel> 
     539<doctrine> 
     540      $this->job = Doctrine::getTable('JobeetJob')-> 
     541       ➥ find($request->getParameter('id')); 
     542</doctrine> 
     543      $this->forward404Unless($this->job); 
     544    } 
     545 
     546<propel> 
     547注意,有些 Propel ~存取器~可以携带参数。因为我们已经定义了 `created_at`  
     548字段的类型为 timestamp,可以设置 `getCreatedAt()` 存取器的第一个参数来格式化日期输出: 
     549 
     550    [php] 
     551    $job->getCreatedAt('m/d/Y'); 
     552</propel> 
     553 
     554>**NOTE** 
     555>工作描述使用 `simple_format_text()` 辅助函数格式化为 HTML,例如将回车替换为 `<br />`。 
     556>因为这个辅助函数属于 `Text` 辅助函数组,而这组辅助函数不会被自动加载, 
     557>因此需要使用 `~use_helper~()` 辅助函数手工加载。 
     558 
     559![Job page](http://www.symfony-project.org/images/jobeet/1_2/04/job.png) 
     560 
     561~槽(Slot)~ 
     562------- 
     563 
     564现在,所有页面的标题已经在 layout 的 `<title>` 标签中定义了: 
     565 
     566    [php] 
     567    <title>Jobeet - Your best job board</title> 
     568 
     569但是对于工作页面,我们希望提供更有用的信息,比如公司名和工作地点。 
     570 
     571在 symfony 中,当 layout 中一个区域依赖模板显示(依赖于模板中定义的内容, 
     572而显示的位置却在模板之外)时,需要定义一个槽: 
     573 
     574![Slots](http://www.symfony-project.org/images/jobeet/1_2/04/layout_slots.png) 
     575 
     576给 layout 添加一个槽,让标题动态显示: 
     577 
     578    [php] 
     579    // apps/frontend/templates/layout.php 
     580    <title><?php include_slot('title') ?></title> 
     581 
     582每个槽都通过一个名字(如:`title`)定义,并通过 `~include_slot~()` 辅助函数调用。 
     583现在,在 `showSuccess.php` 模板开头,使用 `slot()` 为工作页面定义槽的内容: 
     584 
     585    [php] 
     586    // apps/frontend/modules/job/templates/showSuccess.php 
     587    <?php slot( 
     588      'title', 
     589      sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition())) 
     590    ?> 
     591 
     592如果要生成复杂的标题,可以将 `slot()` 辅助函数写成下面的代码块形式: 
     593 
     594    [php] 
     595    // apps/frontend/modules/job/templates/showSuccess.php 
     596    <?php slot('title') ?> 
     597      <?php echo sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition()) ?> 
     598    <?php end_slot(); ?> 
     599 
     600对于像首页这样的页面,只需要使用普通的标题。为了避免在模板中重复书写这些普通的标题, 
     601我们只需要在 layout 中定义一个默认标题即可: 
     602 
     603    [php] 
     604    // apps/frontend/templates/layout.php 
     605    <title> 
     606      <?php if (!include_slot('title')): ?> 
     607        Jobeet - Your best job board 
     608      <?php endif; ?> 
     609    </title> 
     610 
     611如果槽已经被定义,那么 `include_slot()` 辅助函数返回 `true` 。因此,当你在一个模板中 
     612定义了 `title` 槽的内容时,它就会被调用;否则将使用默认的标题。 
     613 
     614>**TIP** 
     615>我们已经看了不少以 `include_` 开头的辅助函数。这些辅助函数可以直接输出 HTML 代码, 
     616>而以 `get_` 开头的辅助函数,大多数时候都只返回内容【需要输出语句显示内容】: 
     617> 
     618>     [php] 
     619>     <?php include_slot('title') ?> 
     620>     <?php echo get_slot('title') ?> 
     621> 
     622>     <?php include_stylesheets() ?> 
     623>     <?php echo get_stylesheets() ?> 
     624 
     625Job 页面的动作 
     626------------------- 
     627 
     628工作页面由 `show` 动作生成,它在 `job` 模块的 `executeShow()` 方法中定义: 
     629 
     630    [php] 
     631    class jobActions extends sfActions 
     632    { 
     633      public function executeShow(sfWebRequest $request) 
     634      { 
     635<propel> 
     636        $this->job = 
     637         ➥ JobeetJobPeer::retrieveByPk($request->getParameter('id')); 
     638</propel> 
     639<doctrine> 
     640        $this->job = Doctrine::getTable('JobeetJob')-> 
     641         ➥ find($request->getParameter('id')); 
     642</doctrine> 
     643        $this->forward404Unless($this->job); 
     644      } 
     645 
     646      // ... 
     647    } 
     648 
     649<propel> 
     650与 `index` 动作类似,此处使用 `JobeetJobPeer` 类的 `retrieveByPk()` 方法获得一条工作记录。 
     651这个方法的参数是一个工作的唯一标识,也就是表的主键。 
     652下一节将会解释为什么 `$request->getParameter('id')` 能返回工作的主键。 
     653</propel> 
     654<doctrine> 
     655与 `index` 动作类似,此处使用 `JobeetJob` 表类的 `find()` 方法获得一条工作记录。 
     656这个方法的参数是一个工作的唯一标识,也就是表的主键。The next section will 
     657下一节将会解释为什么 `$request->getParameter('id')` 能返回工作的主键。 
     658</doctrine> 
     659 
     660<propel> 
     661>**TIP** 
     662>Propel 生成的模型类包含许多有用的方法,这些方法与项目对象一起使用。 
     663>生成的模型类代码位于 `lib/om/` 目录下, 
     664>花些时间浏览一下这些类的各种方法,将会对你很有帮助。 
     665</propel> 
     666 
     667如果数据库中不存在请求的工作记录,我们会将用户带到一个 ~404~ 页面, 
     668这恰好可以使用 `forward404Unless()` 方法来完成。这个方法的第一个参数是布尔型变量,除非为 true, 
     669否则它会停止执行当前进程。当 forward 方法通过抛出 `sfError404Exception` 异常停止执行动作时,不需要使用返回语句。 
     670 
     671对于异常,显示给用户的页面会因生产(`prod`)~环境~和开发(`dev`)~环境~有所区别: 
     672 
     673![404 error in the dev environment](http://www.symfony-project.org/images/jobeet/1_2/05/404_dev.png) 
     674 
     675![404 error in the prod environment](http://www.symfony-project.org/images/jobeet/1_2/05/404_prod.png) 
     676 
     677>**NOTE** 
     678>在部署 Jobeet 网站到生产服务器上之前,你将会学会如何定制默认的 `404` 页面。 
     679 
     680 
     681- 
     682 
     683>**SIDEBAR** 
     684>"~forward~" 系列方法 
     685> 
     686>`forward404Unless` 调用实际上等价于: 
     687> 
     688>     [php] 
     689>     $this->forward404If(!$this->job); 
     690> 
     691>也等价于: 
     692> 
     693>     [php] 
     694>     if (!$this->job) 
     695>     { 
     696>       $this->forward404(); 
     697>     } 
     698> 
     699>`forward404()` 方法自身实际上是如下的快捷方式: 
     700> 
     701>     [php] 
     702>     $this->forward('default', '404'); 
     703> 
     704>`forward()` 方法转发(forward)到同一应用程序的另一个动作; 
     705>在前边的例子中,跳转到 `default` 模块的 `404` 动作。 
     706>`default` 模块绑定在 symfony 中,提供默认动作显示404,安全和登录页面。 
     707 
     708请求与响应 
     709---------------------------- 
     710 
     711当你在浏览器中浏览 `/job` 或  `/job/show/id/1` 页面时,你便启动了一次与 Web 服务器的交互。 
     712浏览器发送一个请求(**~request~** )到服务器而服务器返回一个响应(**~response~**)。 
     713 
     714我们已经看到 symfony 将请求封装到 `sfWebRequest` 对象中(看一下 `executeShow()` 方法)。 
     715作为一个面向对象的框架,响应也被封装到 `sfWebResponse` 对象中。 
     716你可以在一个动作中通过调用 `$this->getResponse()` 访问响应对象。 
     717 
     718这些对象提供许多方便的方法,访问 PHP 函数和 PHP 全局变量中的信息。 
     719 
     720>**NOTE** 
     721>symfony 为什么要打包已经存在的 PHP 功能? 
     722>第一,因为 symfony 方法比 PHP 相应的方法更加强大。 
     723>第二,当你测试一个应用程序时,使用 symfony 方法更容易模拟一个请求或响应对象, 
     724>这比起无味地花时间在全局变量或使用 PHP 中类似 header() 的函数上强很多。 
     725 
     726### 请求 
     727 
     728`sfWebRequest` 类包含了 `~$_SERVER~`, `~$_COOKIE~`, `~$_GET~`, `~$_POST~`, 
     729和 `~$_FILES~` PHP 全局数组: 
     730 
     731 Method name          | PHP equivalent 
     732 -------------------- | -------------------------------------------------- 
     733 `getMethod()`        | `$_SERVER['REQUEST_METHOD']` 
     734 `getUri()`           | `$_SERVER['REQUEST_URI']` 
     735 `getReferer()`       | `$_SERVER['HTTP_REFERER']` 
     736 `getHost()`          | `$_SERVER['HTTP_HOST']` 
     737 `getLanguages()`     | `$_SERVER['HTTP_ACCEPT_LANGUAGE']` 
     738 `getCharsets()`      | `$_SERVER['HTTP_ACCEPT_CHARSET']` 
     739 `isXmlHttpRequest()` | `$_SERVER['X_REQUESTED_WITH'] == 'XMLHttpRequest'` 
     740 `getHttpHeader()`    | `$_SERVER` 
     741 `getCookie()`        | `$_COOKIE` 
     742 `isSecure()`         | `$_SERVER['HTTPS']` 
     743 `getFiles()`         | `$_FILES` 
     744 `getGetParameter()`  | `$_GET` 
     745 `getPostParameter()` | `$_POST` 
     746 `getUrlParameter()`  | `$_SERVER['PATH_INFO']` 
     747 `getRemoteAddress()` | `$_SERVER['REMOTE_ADDR']` 
     748 
     749我们已经用过 `getParameter()` 方法访问请求的参数,它从 `$_GET` 或 `$_POST` 全局变量 
     750或 `~PATH_INFO~` 变量返回值。 
     751 
     752如果你想确定请求参数究竟属于上面哪一类,你需要分别使用 `getGetParameter()`,  
     753`getPostParameter()` 和 `getUrlParameter()` 方法。 
     754 
     755>**NOTE** 
     756>如果你想限制一个动作只接受某种特定 ~HTTP method~ 传递的参数, 
     757>比如,当你需要确定表单是否是通过 `POST` 方式提交的,你可以使用 `isMethod()` 方法: 
     758>`$this->forwardUnless($request->isMethod('POST'));`。 
     759 
     760### 响应 
     761 
     762`sfWebResponse` 类包含了 `~header~()` 和 `setraw~cookie~()` PHP 方法: 
     763 
     764 Method name                   | PHP equivalent 
     765 ----------------------------- | ---------------- 
     766 `setCookie()`                 | `setrawcookie()` 
     767 `setStatusCode()`             | `header()` 
     768 `setHttpHeader()`             | `header()` 
     769 `setContentType()`            | `header()` 
     770 `addVaryHttpHeader()`         | `header()` 
     771 `addCacheControlHttpHeader()` | `header()` 
     772 
     773当然,`sfWebResponse` 类也提供了一种设置响应内容的方法(`setContent()`), 
     774以及发送响应到浏览器的方法(`send()`)。 
     775 
     776今天开始时候,我们讲了如何在 `view.yml` 文件和模板中管理样式表和 JavaScript 脚本。 
     777现在我们用响应对象的 `addStylesheet()` 和 `addJavascript()` 方法也可以实现同样的效果。 
     778 
     779>**TIP** 
     780>[`sfAction`](http://www.symfony-project.org/api/1_2/sfAction), 
     781>[`sfRequest`](http://www.symfony-project.org/api/1_2/sfRequest) 和 
     782>[`sfResponse`](http://www.symfony-project.org/api/1_2/sfResponse) 类 
     783>还提供了许多有用的方法。不要犹豫了,去浏览 
     784>[API documentation](http://www.symfony-project.org/api/1_2/)  
     785>学习更多关于 symfony 内置类的内容。 
     786 
     787明天见 
     788---------------- 
     789 
     790今天,我们已经描述了一些 symfony 使用的设计模式。希望项目的目录结构现在会更有意义。 
     791我们已经通过操作 layout 和模板文件,改变了模板。并使用槽和动作让模板显示的更具动态性。 
     792 
     793明天,我们将学习更多关于今天已经使用到的 `url_for()` 辅助函数的用法,并通过它给子框架设置路由。 
     794 
     795__ORM__ 
     796 
     797}}}