Development

Documentation/zh_CN/jobeet/1.2/04

You must first sign up to be able to contribute.

第 4 天: 控制器和视图

昨天,我们研究了 symfony 是如何通过抽象化数据库引擎间的差别, 将关联成分转化为面向对象的类,来简化数据库管理的。 我们使用 ##ORM## 描述数据库结构、生成数据表,并给数据库添加了一些初始数据。

今天,我们将对昨天生成的基本的 job 模块做进一步的定制。 job 模块已经 包含了 Jobeet 所需的所有代码:

  • 显示所有工作列表的页面
  • 创建新工作的页面
  • 更新已存在工作的页面
  • 删除工作的页面

虽然代码已经有了,但是我们还要修改模板,让它更符合 Jobeet 网页的设计图。

~MVC~ 体系结构

如果你曾经开发 PHP 的网站时没有用过框架,你可能习惯于将 PHP 代码与 HTML 代码混编在同一个 PHP 文件中。 这些 PHP 文件可能会包含类似的结构:初始化和全局配置、与请求页面相关的业务逻辑、 数据库中记录的获取以及最终建立页面的 HTML 代码。

你可能使用过模板引擎从 HTML 代码中分离出业务逻辑, 也可能使用过数据抽象层从业务逻辑中分离出关系模型。 但是大多时候,这只会让你产生大量的难于管理的代码。 虽然开发速度提高了,但不久,你就发现代码越来越难以维护, 尤其是除了你之外没有人能读懂这些代码是如何工作的。

针对上述种种问题,有一个非常好的解决办法。对 Web 开发而言, 现在最普遍使用的组织代码的方式就是 MVC 设计模式。 简单来说,MVC 设计模式定义了一种最自然的组织代码的方式。 这种模式将代码分离为 3个层次:

  • ~模型~层定义业务逻辑(数据库属于这个层)。从昨天的内容你已经知道, symfony 将模型的所有类和与其相关的文件都存放在 lib/model/ 目录下。
  • ~视图~是与用户交互的层(模板引擎是这个层的一部分)。在 symfony 中, 视图层主要由 PHP 模板组成。这些文件存储在不同的 templates/ 目录中, 今天稍后会讲到。
  • ~控制器~是一段代码,它从模型层读取数据传送给视图层,由视图层显示到客户端。 在第1天安装 symfony 时,我们看到所有的请求都由前端控制器(index.php 和 frontend_dev.php) 管理。这些前端控制器(front controllers)将实际工作交由动作(actions)处理。 象我们昨天看到的一样,这些动作被合理的组织到模块(modules)中。

MVC

今天,我将按第2天给出的布局修改主页和工作页,并让它们能动态显示内容。 这样的话,我们要在许多文件上做很多事,你可以从其中了解 symfony 目录结构, 及各层代码分离的方法。

布局(Layout)

首先,如果你仔细研究了网页的设计图,你会发现每个页面都有很多相同的地方。 这表示我们必须在每个页面添加一些相同的代码。如你所知,代码重复是非常糟糕的, 无论是我们谈及的 HTML 还是 PHP 代码,所以我们需要找到一种避免这些公共的视图 元素代码重复的方法。

解决这个问题的一种办法是:将重复代码(如头部代码和底部代码)分别写入单独的页面, 然后再让每个模板去调用这些单独的页面。下面是个简单的图示:

Header and footer

这个办法存在一个缺陷,就是头部和底部两个独立的文件中会含有没有闭合的 HTML 标签。 为了让一切看上去很完美,我们使用了另外一种更好的设计模式来解决这个问题:~装饰~ 设计模式。 装饰设计模式(Decorator design pattern)以另外的方式解决这个问题: 就是使用全局模板(在 symfony 中,称为 ~layout~ ), 来装饰展示内容的模板(译注:内容模板如同画,全局模板如同框,装饰模式就是用画框来装饰画。 反过来说,一个画框又可以装饰不同的画):

Layout

一个应用程序的默认全局模板(layout)是 layout.php , 可以在 apps/frontend/templates/ 目录下找到它。 这个目录中存放的全局模板文件可以被应用程序中的每个模块(module)使用。

用下面的代码替换 symfony 默认的 layout.php:

[php]
<!-- 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>
    <title>Jobeet - Your best job board</title>
    <link rel="shortcut icon" href="/favicon.ico" />
    <?php include_javascripts() ?>
    <?php include_stylesheets() ?>
  </head>
  <body>
    <div id="container">
      <div id="header">
        <div class="content">
          <h1><a href="/job">
            <img src="/images/logo.jpg" alt="Jobeet Job Board" />
          </a></h1>

          <div id="sub_header">
            <div class="post">
              <h2>Ask for people</h2>
              <div>
                <a href="/job/new">Post a Job</a>
              </div>
            </div>

            <div class="search">
              <h2>Ask for a job</h2>
              <form action="" method="get">
                <input type="text" name="keywords"
                  id="search_keywords" />
                <input type="submit" value="search" />
                <div class="help">
                  Enter some keywords (city, country, position, ...)
                </div>
              </form>
            </div>
          </div>
        </div>
      </div>

      <div id="content">
        <?php if ($sf_user->hasFlash('notice')): ?>
          <div class="flash_notice">
            <?php echo $sf_user->getFlash('notice') ?>
          </div>
        <?php endif; ?>

        <?php if ($sf_user->hasFlash('error')): ?>
          <div class="flash_error">
            <?php echo $sf_user->getFlash('error') ?>
          </div>
        <?php endif; ?>

        <div class="content">
          <?php echo $sf_content ?>
        </div>
      </div>

      <div id="footer">
        <div class="content">
          <span class="symfony">
            <img src="/images/jobeet-mini.png" />
            powered by <a href="http://www.symfony-project.org/">
            <img src="/images/symfony.gif" alt="symfony framework" />
            </a>
          </span>
          <ul>
            <li><a href="">About Jobeet</a></li>
            <li class="feed"><a href="">Full feed</a></li>
            <li><a href="">Jobeet API</a></li>
            <li class="last"><a href="">Affiliates</a></li>
          </ul>
        </div>
      </div>
    </div>
  </body>
</html>

与其它的一些模板程序不大相同,symfony 的~模板~都是纯 PHP 文件。 在 layout 模板中包含一些 PHP 变量和函数调用。 如 ~$sf_content~ 就是一个非常有趣的变量:它由框架定义, 包含由动作(action)生成的 HTML 代码。

浏览 job 模块(http://jobeet.localhost/frontend_dev.php/job), 可以看到所有被 layout 装饰的动作。

样式、图片和 javascript

因为这个教程不涉及 Web 设计的内容,所以我们已经为 Jobeet 准备好了所有要使用到的 资产(asset)文件: 下载图片文件 包然后将其解压缩到 web/images/ 目录; 下载样式文件 包然后将其解压缩到 web/css/ 目录。

NOTE 在布局中,还包含了一个 favicon。你可以 下载 Jobeet 的 favicon 然后将其放到 web/ 目录。

The job module with a layout and assets

TIP 默认的情况下,generate:project 任务会自动在 web/ 目录下生成三个子目录用来 分别存放项目的不同资产文件: web/images/ 目录用来保存图片, web/~css~/ 保存 css ~样式表~,web/js/ 保存 ~JavaScript~ 文件。 这是 symfony 定义的众多~约定(convention)~之一,如果没有特别的声明, symfony 会自动到这些目录寻找相应类型的文件。 当然,你也可以通过显示的说明,将这些类型文件放到 web/ 下的任意目录中。

精明的读者会注意到,虽然在默认的布局文件中并没有提及 main.css 文件, 但是他却明确地出现在所生成的 HTML 中。而且除了这个样式文件的引用之外并无其他。 这是怎么回事呢?

样式文件已经在 <head> 标签之内的 ~include_stylesheets~() 函数调用了。 include_stylesheets() 函数是一个辅助函数(helper)。 辅助函数是 symfony 中内置的函数,它可以传送参数并生成 HTML 代码。 很多情况下辅助函数对模板中频繁使用的代码片段打包并节约书写 HTML 代码的时间。 include_stylesheets() 辅助函数用来生成一个调用 css 文件的 <link> 标签。

但是辅助函数是如何知道调用哪个样式文件呢?

~视图~ 层可以通过编辑应用程序的 ~view.yml~ 配置文件加以配置。 下面是 generate:app 任务自动生成的 ~view.yml~ 文件:

[yml]
# apps/frontend/config/view.yml
default:
  http_metas:
    content-type: text/html

  metas:
    #title:        symfony project
    #description:  symfony project
    #keywords:     symfony, project
    #language:     en
    #robots:       index, follow

  stylesheets:    [main.css]

  javascripts:    []

  has_layout:     on
  layout:         layout

这个 view.yml 文件为应用程序下的所有模板设置默认配置。 例如 stylesheets 项定义了应用程序每个页面要包含的样式文件的一个数组 (这个包含由 include_stylesheets() 辅助函数完成)。

NOTE 在默认的 view.yml 配置文件中,被引用的文件是 main.css 而不是 /css/main.css。 实际上,两者是等价的,因为对于样式文件而言 symfony 的路径前缀是相对于 /~css~/ 的。

当定义多个样式时,symfony 会按照数组中文件名的顺序,依次包含每个样式文件:

[yml]
stylesheets:    [main.css, jobs.css, job.css]

你可以改变它们的 media 属性,也可以省略样式文件名的 .css 后缀:

[yml]
stylesheets:    [main.css, jobs.css, job.css, print: { media: print }]

这个配置将处理为如下的代码:

[php]
<link rel="stylesheet" type="text/css" media="screen"
  href="/css/main.css" />
<link rel="stylesheet" type="text/css" media="screen"
  href="/css/jobs.css" />
<link rel="stylesheet" type="text/css" media="screen"
  href="/css/job.css" />
<link rel="stylesheet" type="text/css" media="print"
  href="/css/print.css" />

TIP view.yml 配置文件也定义应用程序使用的 ~layout~。 默认为 layout 模板,因此 symfony 使用 layout.php 文件装饰每个页面。 如果将 ~has_layout~ 项设置为 false,装饰过程将被禁用。

It works as is but the jobs.css file is only needed for the homepage and the job.css file is only needed for the job page. view.yml 配置文件能以每模块为基础进行定制。 将应用程序的 view.yml 文件改回只包含 main.css 文件:

[yml]
# apps/frontend/config/view.yml
stylesheets:    [main.css]

job 模块定制视图,在 apps/frontend/modules/job/config/ 目录下创建 view.yml 文件:

[yml]
# apps/frontend/modules/job/config/view.yml
indexSuccess:
  stylesheets: [jobs.css]

showSuccess:
  stylesheets: [job.css]

indexSuccessshowSuccess 部分之下(他们是与 indexshow 动作相联系的模板名称,关于模板的命名规则后面会详细讲述), 你能重新定制任何应用程序的 view.yml 配置文件的 default 部分之下的任何配置条目。 所有定制的配置条目会与应用程序的 view.yml 中的配置条目合并在一起。 (合并的原则是:模块中所有的配置条目及模块中没有而应用程序中有的配置条目将全部保留; 应用程序与模块中相同的配置条目将被模块中的配置条目覆盖。) 你也可以用特别的 all 部分为一个模块的所有动作定义某些配置。

SIDEBAR symfony 的配置原则

在众多的 symfony ~配置~文件中, 相同的设置可以在不同的级别中定义:

  • 默认配置(default configuration)位于 symfony 框架中
  • 项目的全局配置(global configuration)位于 config/ 目录下
  • 应用程序的局部配置(local configuration)位于 apps/APP/config/ 目录下
  • 模块的局部配置位于 apps/APP/modules/MODULE/config/ 目录下

为获得良好的性能,程序运行时,配置系统将合并这些存在于不同文件中的设置, 并生成一个缓存文件。这就是为什么改变设置时需要清空缓存的原因。

一般来说,配置文件中的每个设置都可以使用 PHP 代码的动态实现。 例如,你可以在模板中直接使用 ~use_stylesheet~() 辅助函数包含一个样式表来 替换 job 模块的 view.yml 中的设置:

[php]
<?php use_stylesheet('main.css') ?>

你也可以在全局模板中使用这个辅助函数实现全局样式表的包含。

选择哪种方法只是一个使用偏好问题。一方面,view.yml 提供了一种为模块里所有动作设置配置的方法, 这在一个模板里是不可能实现的(模板中只能配置使用这个模板的动作),但这种配置是完全静态的。 另一方面,使用 use_stylesheet() ~辅助函数~ 会非常灵活,另外使用辅助函数会使样式表定义和 HTML 代码放在一起,修改起来会方便许多。

在 Jobeet 中,我们将使用 use_stylesheet() 辅助函数的方式,所以你可以删除 view.yml 文件。 我们仅仅创建并更新使用 use_stylesheet() 调用的 job 模板:

[php]
<!-- apps/frontend/modules/job/templates/indexSuccess.php -->
<?php use_stylesheet('jobs.css') ?>

<!-- apps/frontend/modules/job/templates/showSuccess.php -->
<?php use_stylesheet('job.css') ?>

NOTE 类似地, JavaScript 配置可以通过设置 view.yml 配置文件中的 javascripts 配置条目完成, 也可以通过在模板使用 ~use_javascript~() 辅助函数来定义 JavaScript 文件的包含。

Job 的首页

正如第3天看到的,job首页已经通过 job 模块中 index 动作生成。 index 动作是首页的控制器部分,关联的模板 indexSuccess.php 是视图部分:

apps/
  frontend/
    modules/
      job/
        actions/
          actions.class.php
        templates/
          indexSuccess.php

动作

每个 ~action~ 都表现为一个类的方法。 对于 job 首页而言, 类是 jobActions (命名方式:模块名+Action后缀),方法是 executeIndex() (命名方式:execute+以驼峰式书写的动作名)。 这个动作从数据库中获取全部的工作记录:

[php]
// apps/frontend/modules/job/actions/actions.class.php
class jobActions extends sfActions
{
  public function executeIndex(sfWebRequest $request)
  {

$this->jobeet_job_list = JobeetJobPeer::doSelect(new Criteria()); $this->jobeet_job_list = Doctrine::getTable('JobeetJob') ->createQuery('a') ->execute(); }

  // ...
}

让我们仔细看一下这段代码: executeIndex() 方法(控制器)调用模型的 JobeetJobPeer 方法获取所有工作记录(new Criteria()),这个方法返回一个包含所有 JobeetJob 对象 【symfony 的 ORM 将数据表中每条记录转化成一个对象】的数组,并将这个数组赋值给 jobeet_job_list 对象属性。 让我们仔细看一下这段代码: executeIndex() 方法(控制器)调用表 JobeetJob 生成一个查询 获取所有工作的记录。返回 JobeetJob 对象的 Doctrine_Collection ,并将其赋值给 jobeet_job_list 对象属性。

所有这些对象属性(jobeet_job_list)都会自动传递给模板(视图)。 要将数据从控制器传送到视图【或者说从动作传给模板】,只需要定义一个新属性:

[php]
public function executeFooBar(sfWebRequest $request)
{
  $this->foo = 'bar';
  $this->bar = array('bar', 'baz');
}

你可以直接在模板中用 $foo$bar 变量访问这个两个属性。

模板

默认情况下,~模板~ 名称与一个动作的名称是相联系的(模板的命名规则:动作名+Success后缀)。

indexSuccess.php 模板为所有工作生成 HTML 表格用来展示工作信息,下面是当前模板的代码:

[php]
<!-- apps/frontend/modules/job/templates/indexSuccess.php -->
<?php use_stylesheet('jobs.css') ?>

<h1>Job List</h1>

<table>
  <thead>
    <tr>
      <th>Id</th>
      <th>Category</th>
      <th>Type</th>
<!-- more columns here -->
      <th>Created at</th>
      <th>Updated at</th>
    </tr>
  </thead>
  <tbody>
    <?php foreach ($jobeet_job_list as $jobeet_job): ?>
    <tr>
      <td>
        <a href="<?php echo url_for('job/show?id='.$jobeet_job->getId()) ?>">
          <?php echo $jobeet_job->getId() ?>
        </a>
      </td>
      <td><?php echo $jobeet_job->getCategoryId() ?></td>
      <td><?php echo $jobeet_job->getType() ?></td>
<!-- more columns here -->
      <td><?php echo $jobeet_job->getCreatedAt() ?></td>
      <td><?php echo $jobeet_job->getUpdatedAt() ?></td>
    </tr>
    <?php endforeach; ?>
  </tbody>
</table>

<a href="<?php echo url_for('job/new') ?>">New</a>

在模板代码中, foreach 遍历所有 Job 对象($jobeet_job_list),并输出数据表中 每条工作记录中每个字段(column)的值。 记住,我们可以通过调用存取器、很容易的访问记录中的每个字段,存取器有统一的命名规则: 是以 get+驼峰式书写的字段名(例如 getCreatedAt() 方法可以获取一条记录的 created_at 字段的值)。

我们修改一下模板,只显示需要的内容:

[php]
<!-- apps/frontend/modules/job/templates/indexSuccess.php -->
<?php use_stylesheet('jobs.css') ?>

<div id="jobs">
  <table class="jobs">
    <?php foreach ($jobeet_job_list as $i => $job): ?>
      <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>">
        <td class="location"><?php echo $job->getLocation() ?></td>
        <td class="position">
          <a href="<?php echo url_for('job/show?id='.$job->getId()) ?>">
            <?php echo $job->getPosition() ?>
          </a>
        </td>
        <td class="company"><?php echo $job->getCompany() ?></td>
      </tr>
    <?php endforeach; ?>
  </table>
</div>

Homepage

模板中的 url_for() 也是一个辅助函数,我们明天再说它。

Job 页面的模板

现在我们定制 job 页的模板,打开 showSuccess.php 文件,用下面代码替换现有的内容:

[php]
<!-- apps/frontend/modules/job/templates/showSuccess.php -->
<?php use_stylesheet('job.css') ?>
<?php use_helper('Text') ?>

<div id="job">
  <h1><?php echo $job->getCompany() ?></h1>
  <h2><?php echo $job->getLocation() ?></h2>
  <h3>
    <?php echo $job->getPosition() ?>
    <small> - <?php echo $job->getType() ?></small>
  </h3>

  <?php if ($job->getLogo()): ?>
    <div class="logo">
      <a href="<?php echo $job->getUrl() ?>">
        <img src="/uploads/jobs/<?php echo $job->getLogo() ?>"
          alt="<?php echo $job->getCompany() ?> logo" />
      </a>
    </div>
  <?php endif; ?>

  <div class="description">
    <?php echo simple_format_text($job->getDescription()) ?>
  </div>

  <h4>How to apply?</h4>

  <p class="how_to_apply"><?php echo $job->getHowToApply() ?></p>

  <div class="meta">

posted on <?php echo $job->getCreatedAt('m/d/Y') ?> posted on <?php echo date('m/d/Y', strtotime($job->getCreatedAt())) ?>

  <div style="padding: 20px 0">
    <a href="<?php echo url_for('job/edit?id='.$job->getId()) ?>">
      Edit
    </a>
  </div>
</div>

这个模板直接在动作中使用 $job 变量显示工作信息。我们前边提到过,这个变量是在动作里定义的一个对象属性。 因为我们将 $jobeet_job 改成了 $job,所以要相应的修改 show 动作里的对象属性(注意,有两个变量要修改):

[php]
// apps/frontend/modules/job/actions/actions.class.php
public function executeShow(sfWebRequest $request)
{

$this->job = ➥ JobeetJobPeer::retrieveByPk($request->getParameter('id')); $this->job = Doctrine::getTable('JobeetJob')-> ➥ find($request->getParameter('id')); $this->forward404Unless($this->job); }

注意,有些 Propel ~存取器~可以携带参数。因为我们已经定义了 created_at 字段的类型为 timestamp,可以设置 getCreatedAt() 存取器的第一个参数来格式化日期输出:

[php]
$job->getCreatedAt('m/d/Y');

NOTE 工作描述使用 simple_format_text() 辅助函数格式化为 HTML,例如将回车替换为 <br />。 因为这个辅助函数属于 Text 辅助函数组,而这组辅助函数不会被自动加载, 因此需要使用 ~use_helper~() 辅助函数手工加载。

Job page

~槽(Slot)~

现在,所有页面的标题已经在 layout 的 <title> 标签中定义了:

[php]
<title>Jobeet - Your best job board</title>

但是对于工作页面,我们希望提供更有用的信息,比如公司名和工作地点。

在 symfony 中,当 layout 中一个区域依赖模板显示(依赖于模板中定义的内容, 而显示的位置却在模板之外)时,需要定义一个槽:

Slots

给 layout 添加一个槽,让标题动态显示:

[php]
// apps/frontend/templates/layout.php
<title><?php include_slot('title') ?></title>

每个槽都通过一个名字(如:title)定义,并通过 ~include_slot~() 辅助函数调用。 现在,在 showSuccess.php 模板开头,使用 slot() 为工作页面定义槽的内容:

[php]
// apps/frontend/modules/job/templates/showSuccess.php
<?php slot(
  'title',
  sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition()))
?>

如果要生成复杂的标题,可以将 slot() 辅助函数写成下面的代码块形式:

[php]
// apps/frontend/modules/job/templates/showSuccess.php
<?php slot('title') ?>
  <?php echo sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition()) ?>
<?php end_slot(); ?>

对于像首页这样的页面,只需要使用普通的标题。为了避免在模板中重复书写这些普通的标题, 我们只需要在 layout 中定义一个默认标题即可:

[php]
// apps/frontend/templates/layout.php
<title>
  <?php if (!include_slot('title')): ?>
    Jobeet - Your best job board
  <?php endif; ?>
</title>

如果槽已经被定义,那么 include_slot() 辅助函数返回 true 。因此,当你在一个模板中 定义了 title 槽的内容时,它就会被调用;否则将使用默认的标题。

TIP 我们已经看了不少以 include_ 开头的辅助函数。这些辅助函数可以直接输出 HTML 代码, 而以 get_ 开头的辅助函数,大多数时候都只返回内容【需要输出语句显示内容】:

[php]
<?php include_slot('title') ?>
<?php echo get_slot('title') ?>

<?php include_stylesheets() ?>
<?php echo get_stylesheets() ?>

Job 页面的动作

工作页面由 show 动作生成,它在 job 模块的 executeShow() 方法中定义:

[php]
class jobActions extends sfActions
{
  public function executeShow(sfWebRequest $request)
  {

$this->job = ➥ JobeetJobPeer::retrieveByPk($request->getParameter('id')); $this->job = Doctrine::getTable('JobeetJob')-> ➥ find($request->getParameter('id')); $this->forward404Unless($this->job); }

  // ...
}

index 动作类似,此处使用 JobeetJobPeer 类的 retrieveByPk() 方法获得一条工作记录。 这个方法的参数是一个工作的唯一标识,也就是表的主键。 下一节将会解释为什么 $request->getParameter('id') 能返回工作的主键。 index 动作类似,此处使用 JobeetJob 表类的 find() 方法获得一条工作记录。 这个方法的参数是一个工作的唯一标识,也就是表的主键。The next section will 下一节将会解释为什么 $request->getParameter('id') 能返回工作的主键。

>TIP >Propel 生成的模型类包含许多有用的方法,这些方法与项目对象一起使用。 >生成的模型类代码位于 lib/om/ 目录下, >花些时间浏览一下这些类的各种方法,将会对你很有帮助。

如果数据库中不存在请求的工作记录,我们会将用户带到一个 ~404~ 页面, 这恰好可以使用 forward404Unless() 方法来完成。这个方法的第一个参数是布尔型变量,除非为 true, 否则它会停止执行当前进程。当 forward 方法通过抛出 sfError404Exception 异常停止执行动作时,不需要使用返回语句。

对于异常,显示给用户的页面会因生产(prod)~环境~和开发(dev)~环境~有所区别:

404 error in the dev environment

404 error in the prod environment

NOTE 在部署 Jobeet 网站到生产服务器上之前,你将会学会如何定制默认的 404 页面。

-

SIDEBAR "~forward~" 系列方法

forward404Unless 调用实际上等价于:

[php]
$this->forward404If(!$this->job);

也等价于:

[php]
if (!$this->job)
{
  $this->forward404();
}

forward404() 方法自身实际上是如下的快捷方式:

[php]
$this->forward('default', '404');

forward() 方法转发(forward)到同一应用程序的另一个动作; 在前边的例子中,跳转到 default 模块的 404 动作。 default 模块绑定在 symfony 中,提供默认动作显示404,安全和登录页面。

请求与响应

当你在浏览器中浏览 /job/job/show/id/1 页面时,你便启动了一次与 Web 服务器的交互。 浏览器发送一个请求(~request~ )到服务器而服务器返回一个响应(~response~)。

我们已经看到 symfony 将请求封装到 sfWebRequest 对象中(看一下 executeShow() 方法)。 作为一个面向对象的框架,响应也被封装到 sfWebResponse 对象中。 你可以在一个动作中通过调用 $this->getResponse() 访问响应对象。

这些对象提供许多方便的方法,访问 PHP 函数和 PHP 全局变量中的信息。

NOTE symfony 为什么要打包已经存在的 PHP 功能? 第一,因为 symfony 方法比 PHP 相应的方法更加强大。 第二,当你测试一个应用程序时,使用 symfony 方法更容易模拟一个请求或响应对象, 这比起无味地花时间在全局变量或使用 PHP 中类似 header() 的函数上强很多。

请求

sfWebRequest 类包含了 ~$_SERVER~, ~$_COOKIE~, ~$_GET~, ~$_POST~, 和 ~$_FILES~ PHP 全局数组:

Method name | PHP equivalent -------------------- | -------------------------------------------------- getMethod() | $_SERVER['REQUEST_METHOD'] getUri() | $_SERVER['REQUEST_URI'] getReferer() | $_SERVER['HTTP_REFERER'] getHost() | $_SERVER['HTTP_HOST'] getLanguages() | $_SERVER['HTTP_ACCEPT_LANGUAGE'] getCharsets() | $_SERVER['HTTP_ACCEPT_CHARSET'] isXmlHttpRequest() | $_SERVER['X_REQUESTED_WITH'] == 'XMLHttpRequest' getHttpHeader() | $_SERVER getCookie() | $_COOKIE isSecure() | $_SERVER['HTTPS'] getFiles() | $_FILES getGetParameter() | $_GET getPostParameter() | $_POST getUrlParameter() | $_SERVER['PATH_INFO'] getRemoteAddress() | $_SERVER['REMOTE_ADDR']

我们已经用过 getParameter() 方法访问请求的参数,它从 $_GET$_POST 全局变量 或 ~PATH_INFO~ 变量返回值。

如果你想确定请求参数究竟属于上面哪一类,你需要分别使用 getGetParameter(), getPostParameter()getUrlParameter() 方法。

NOTE 如果你想限制一个动作只接受某种特定 ~HTTP method~ 传递的参数, 比如,当你需要确定表单是否是通过 POST 方式提交的,你可以使用 isMethod() 方法: $this->forwardUnless($request->isMethod('POST'));

响应

sfWebResponse 类包含了 ~header~()setraw~cookie~() PHP 方法:

Method name | PHP equivalent ----------------------------- | ---------------- setCookie() | setrawcookie() setStatusCode() | header() setHttpHeader() | header() setContentType() | header() addVaryHttpHeader() | header() addCacheControlHttpHeader() | header()

当然,sfWebResponse 类也提供了一种设置响应内容的方法(setContent()), 以及发送响应到浏览器的方法(send())。

今天开始时候,我们讲了如何在 view.yml 文件和模板中管理样式表和 JavaScript 脚本。 现在我们用响应对象的 addStylesheet()addJavascript() 方法也可以实现同样的效果。

TIP [sfAction](http://www.symfony-project.org/api/1_2/sfAction), [sfRequest](http://www.symfony-project.org/api/1_2/sfRequest) 和 [sfResponse](http://www.symfony-project.org/api/1_2/sfResponse) 类 还提供了许多有用的方法。不要犹豫了,去浏览 API documentation 学习更多关于 symfony 内置类的内容。

明天见

今天,我们已经描述了一些 symfony 使用的设计模式。希望项目的目录结构现在会更有意义。 我们已经通过操作 layout 和模板文件,改变了模板。并使用槽和动作让模板显示的更具动态性。

明天,我们将学习更多关于今天已经使用到的 url_for() 辅助函数的用法,并通过它给子框架设置路由。

ORM