List of Posts
Date | Title | %s | \n", $row['date']); printf("\t\t%s | \n", $row['title']); echo "\t\n"; } ?>
---|
List of Posts
Date | Title |
---|---|
List of Posts
Date | Title |
---|---|
List of Posts
Date | Title |
---|---|
getDate() ?> | getTitle() ?> |
Application error
symfony applicationfailed to start properly" 现在可以访问你的应用程序了。你可以通过这个 URL 访问 symfony 成功页面: http://www.example.com/myapp_dev.php/ SIDEBAR 其他服务器配置 symfony 和其他服务器配置兼容。例如,你可以用 alias 代替虚 拟主机来访问 symfony 程序。 你也能在 IIS 上运行 symfony 程 序。关于配置有很多技巧, 本书并不准备解释所有的技巧。 想要找到具体的服务器配置指南,可以参考 symfony 的 wiki (http://www.symfony-project.com/trac/wiki),这里有详尽的 指导。 安装问题 这些通常会有错误说明,甚至会提供网络上针对此问题的资源连接。如果在安 装中遇到问题,尽量把错误或例外显示在 shell 或者浏览器上。 常见问题 如果你还是无法运行 symfony,检查以下几点: · 一些 PHP 环境同时包含了 PHP4 和 PHP5 的命令。 因此,在命令行用 php5 替代 php, 也就是说试着用 php5 symfony 代替 symfony。你也许 在.htaccess 配置中需要增加 SetEnv PHP_VER 5 参数,或者把 web/目录 中的.php 换成.php5。在 PHP4 命令行下试着访问 symfony 就会有类似下 面的提示: Parse error, unexpected ',', expecting '(' in .../symfony.php on line 19. · 在 php.ini 中的内存限制,至少需要设置为 16M。 通常的症状就是通过 PEAR 方式安装 symfony 的时候出现的错误信息。 Allowed memory size of 8388608 bytes exhausted · 必须在 php.ini 中把 zend.ze1_compatibility_mode 参数设置为 off。 否则通过浏览器去访问脚本的话会出现"implicit cloning"错误: Strict Standards: Implicit cloning object of class 'sfTimer'because of 'zend.ze1_compatibility_mode' · 在你的项目中位于 Web 服务器上的 log/和 cache/目录必须是可写的。如 果在没有正确设定的时候访问 symfony 程序会出现以下提示: sfCacheException [message] Unable to write cache file"/usr/myproject/cache/frontend/prod/config/config_config_handl ers.yml.php" · 系统的路径需要包含 php 命令的路径,你的 php.ini 的包含路径必须包 括 PEAR 的路径(如果你使用 PEAR)。 · 有时,服务器上会有多个 php.ini 文件(例如,如果你使用 WAMP 包)。 可以用 phpinfo()函数去了解程序所用的 php.ini 文件所在的确切位 置。 NOTE 虽然不是强制性的,但是这里强烈推荐,为了运行得更顺 畅,在 php.ini 中设置 magic_quotes_gpc 和 register_globals 参数为 off。 symfony 资源 你可以在这些地方找到一些已经发现的问题的答案: · symfony 安装论坛 (http://www.symfony-project.com/forum/) 这里有 各种平台,环境配置,主机上安装 symfony 的问题讨论。 · 用户邮件列表档案(http://groups.google.fr/group/symfony-users) 也可以搜索。你也许会找到一些人遇到同样的问题。 · symfony wiki (http://www.symfony- project.com/trac/wiki#Installingsymfony) 有由 symfony 用户提供的 详细安装教程。 如果没有找到答案,试着把问题放到 symfony 社区。你可以在论坛,邮件列表 甚至在#symfony IRC 频道得到大家的回应。 源代码版本控制 设置程序完成后,推荐进行版本控制。 版本控制能跟踪对代码的所有修改,可 以回退到以前的版本,更容易地给程序打补丁和更有效地进行团队合作开发。 symfony 生来就支持 CVS,虽然更推荐使用 Subversion (http://subversion.tigris.org/)。下面的例子展示了 Subversion 的命令, 我们假设你已经有 Subversion 服务器并且希望在项目中建立一个新的版本库。 Windows 使用者推荐用叫做 TortoiseSVN (http://tortoisesvn.tigris.org/) 的 Subversion 客户端。在 Subversion 文档中可以找到关于版本控制命令的更 多信息。 下面的例子假设系统环境参数中已经定义了 $SVNREP_DIR。 如果还没有定义, 你要以实际存放位置代替 $SVNREP_DIR。 让我们在 myproject 项目中建立一个新的版本库: > svnadmin create $SVNREP_DIR/myproject 建立 trunk、 tags 和 branches 作为版本库的基础结构(layout)用以下命令: > svn mkdir -m "layout creation" file:///$SVNREP_DIR/myproject/trunk file:///$SVNREP_DIR/myproject/tags file:///$SVNREP_DIR/myproject/branches 这会是你的第一个版本。现在你需要把项目中除 cache/ 和 log/ 目录之外所 有的文件导入版本库: > cd ~/myproject > rm -rf cache/* > rm -rf log/* > svn import -m "initial import" . file:///$SVNREP_DIR/myproject/trunk 输入以下命令检查已经提交的文件: > svn ls file:///$SVNREP_DIR/myproject/trunk/ 看上去没问题。现在 SVN 库包含了你所有的项目文件的参考版本(还有历 史)。这意味着~myproject/目录需要与 SVN 库关联。要实现关联,首先修改 myproject/目录的名字(如果一切正常你很快就可以删了它了)然后在一个新 目录里签出 SVN 库里的文件: > cd ~ > mv myproject myproject.origin > svn co file:///$SVNREP_DIR/myproject/trunk myproject > ls myproject 现在你可以改写~/myproject/下的文件并提交到版本库中去。 myproject.origin/目录已经没用了,别忘了把它删除掉。 还有一件事情需要配置。 如果你提交当前工作目录到版本库中, 也许包含了 一些无用的文件,例如项目中的 cache 和 log 目录。所以必须为这个项目设置 一个 SVN 忽略列表。当然,你还需要重新设置 cache/ 和 log/目录的权限: > cd ~/myproject > chmod 777 cache > chmod 777 log > svn propedit svn:ignore log > svn propedit svn:ignore cache SVN 默认的文字编辑器会启动。如果没有,在 Subversion 中设置你想用的文字 编辑器: > export SVN_EDITOR=Hello, world!
".time().""; } ?> 例 4-5 - 另类 PHP 语法,适合于模板Hello, world!
TIP 一般来说模板语法的可读性是否够强是看这个文件是否不包含 PHP 的 echo 语句或者"{}"。大多数时候,开始的在同一行。 从动作传递信息给模板 动作要做的事情是所有的复杂计算,取出数据,测试,为模板设定显示或者测试 用的变量。symfony 让动作类的属性(动作里的可以通过$this->variableName 访问)能够直接在模板里面的全局命名空间里面访问得到(通过$variableName)。 例 4-6 与 4-7 演示如何从动作传递信息给模板。 例 4-6 - 设定动作的一个属性,把它传给模板 hour = $today['hours']; } } 例 4-7 - 模板能直接访问动作的属性Hello, world!
= 18): ?>Or should I say good evening? It's already .
NOTE 有几个数据可以直接在模板中访问而不需要在动作里面设置。每个模板都 可以执行$sf_contex,$sf_request,$sf_params 还有$sf_user 对象的方法。它 们 包 含 当前上下文、请求、请求参数还有 session 的信息。不久你就能学会怎么有 效的利用它们。 从用户表单取得数据 表单是从用户取得信息的好方法。用 HTML 写表单的元素有时会很麻烦,特别是 你想要XHTML兼容时。你可以按照平常的方式在 symfony 模板里面使用表单元素, 如例 4-8 所示,不过 symfony 提供了一些辅助函数来简化这个任务。 例 4-8 - 模板可以包含普通的 HTML 代码Hello, world!
= 18): ?>Or should I say good evening? It's already .
辅助函数是 symfony 定义的用在模板里的函数。它输出 HTML 代码从而节省你写 HTML 代码的时间。使用 symfony 辅助函数,你可以用例 4-9 的代码达到与例 4-8 同样的结果。 例 4-9 - 用辅助函数比写 HTML 标签更快更容易Hello, world!
= 18): ?>Or should I say good evening? It's already .
SIDEBAR 辅助函数是来帮助你的。 如果,你认为在例 4-9 的例子里,辅助函数的版本没有写 HTML 快,看看这个例 子: $card_list = array( > 'VISA' => 'Visa', > 'MAST' => 'MasterCard', > 'AMEX' => 'American Express', > 'DISC' => 'Discover'); > echo select_tag('cc_type', options_for_select($card_list, 'AMEX')); > ?> 上面的代码的 HTML 输出如下: 在模板里使用辅助函数使编写代码的速度提高,代码更清晰,更简洁。唯一的代 价是需要花时间学习他们,学习过程将一直持续到本书完结,到你在你习惯的编 辑器中用快捷键写的时候。所以如果不会用 symfony 的辅助函数,你仍然可以继 续使用 HTML 标签,不过这很浪费也很枯燥。 注意我们不推荐专业 web 开发者使用短开始标签(=,等效于 I never say my name 为了避免这样的麻烦,请使用 link_to()辅助函数来建立所有的链接到应用程序 内部的动作的超链接。例 4-11 演示了如何使用超链接辅助函数。 例 4-11 - link_to() 辅助函数Hello, world!
= 18): ?>Or should I say good evening? It's already .
上面的代码生成的 HTML 与前一个例子完全一样,但是如果修改路由规则,所有 的模板会根据规则重新格式 URL。 link_to()辅助函数,与很多辅助函数类似,接受另一个特殊的参数,这个参数 用来传递 HTML 标签属性。例 4-12 是一个 option 属性的例子还有生成的 HTML。 option 参数可以是一个数组或者一个简单的由几个 key=value 与空格组成的字 符串。 例 4-12 - 大多数辅助函数有 Option 参数 // 用数组作 option 参数 'special_link', 'confirm' => 'Are you sure?', 'absolute' => true )) ?> // 用字符串作 option 参数 // 结果一样 => I never say my name 任何使用 symfony 辅助函数输出 HTML 标签的时候,都可以在 option 参数中加入 额外的属性(例如例 4-12 中的 class 属性)。你甚至可以用 HTML 4.0 的"快速而 肮脏(quick-and-dirty)"的方式(不写双引号),symfony 会用漂亮的 XHTML 方式 输出。这是用辅助函数比写 HTML 快的又一个原因。 NOTE 由于需要额外的解析与转换,字符串形式比数组要慢。 与其它辅助函数类似,链接辅助函数有好几种形式与参数。第 9 章将向你详细介 绍这些内容。 从请求中取得信息 无论用户通过表单(通常是 POST 请求)还是通过 URL(GET 请求) 取得信息,你都 可以在动作中通过 sfActions 对象的 getRequestParameter()方法取得相关的数 据。例 4-13 演示了如何在 actionAction 中取得 name 参数的值。 例 4-13 - 在动作中取得请求参数的值 name = $this->getRequestParameter('name'); } } 如果数据操作很简单,你甚至不必用动作来取得参数值。模板可以直接通过 $sf_params 的 get()方法来取得参数的值,类似于动作中的 getRequestParameter()方法。 如果 executeAnotherAction() 方法是空的, 例 4-14 中的这种方法也可以从 anotherActionSuccess.php 模板中取到 name 参数的值。 例 4-14 - 直接从模板中取得参数的值Hello, get('name') ?>!
NOTE 为什么不直接使用$_POST,$_GET, 或 $_REQUEST 变量呢?因为如果你的 URL 的格式会变化(例如 http://localhost/articles/europe/france/finance.html ,没有?或者=),这 样 这些 PHP 变量就不管用了,只有路由系统能够取得请求参数。还 有 你 可能需要 输入过滤器来防止恶意代码注入,只有保持所有的参数使用一个干净的参数存储 器的时候才能实现。 $sf_params 对象的作用仅仅是数组的替代品。例如,如果你想判断一个请求参 数是否存在,你可以只用$sf_params->has()方法而不必用 get()方法取得实际 的值,如例 4-15。 例 4-15 - 在模板中判断一个参数是否存在 has('name')): ?>Hello, get('name') ?>!
Hello, John Doe!
你可能已经猜到这用一行代码就可以完成。与 symfony 里面的大多数 getter 方 法一样,动作里的 getRequestParameter()还有模板里的$sf_params->get()方 法(实际上两者调用的是同一个对象的同一个方法)可以有第二个参数:默认值, 在参数不存在的时候起作用。Hello, get('name', 'John Doe') ?>!
总结 在 symfony 里面,页面由一个动作(actions/actions.class.php 文件里的一个 方法,以 execute 开头)还有一个模板(templates/目录里的一个文件,通常以 Success.php 结尾)组成。功能有关联的页面组成模块。写模板有辅助函数帮忙, 辅助函数是 symfony 提供的返回 HTML 代码的函数。并且你需要把 URL 考虑成回 应的一部分,URL 也可以根据需要重新安排格式,所以你需要避免绕过超链接辅 助方法直接写动作的 URL。 一旦了解了这些基本原理,你就可以开始用 symfony 写一个完整的 web 应用程序 了。但是这需要花很长时间,因为几乎所有的功能都可以通过 symfony 的某种功 能来简化开发……,所以这本书还没结束。 第 5 章 配置 symfony 为了达到简单易用的目的,symfony 定义了一些惯例,这些惯例能够满足大多数 情况的需求。另一方面,使用一系列简单而强大的配置文件,我们可以定制这个 框架及应用程序的几乎所有的地方。使用这些配置文件可以为程序增加一些特殊 的参数。 这一章介绍配置系统如何工作: · symfony 配置信息保存在 YAML 格式的文件里,当然也可以换成其它格式。 · 配置文件分成项目、应用程序、模块这几个等级,分别存放在项目目录中 对应子目录里。 · 你可以定义几套不同的配置文件,symfony 里面的一套配置文件称之为环 境。 · 配置文件里面定义的值可以在 PHP 代码里面取得。 · 另外,symfony 可以识别 YAML 里面的 PHP 代码等,这使得配置系统更灵 活。 配置系统 不论什么用途,大多数 web 应用程序都有一些共同的特征。例如,有些区域只允 许一部分用户访问;很多页面共用一个布局;表单填写验证失败后自动把用户输 入的内容放进表单。框架定义了一些实现这些特性的结构,开发者通过配置系统 进一步的调整它们。这 种策略可以节省大量开发时间,因为很多改变并不用改写 代码,尽管实现这些改变需要很多代码。这种策略也更有效率,因为这些信息可 以存放在容易识别的位置。 但是,这样的做法有两个严重缺点: · 开发者最后整天不停的写复杂的 XML 文件。 · 使用 PHP 处理每个请求花费的时间更多了。 考虑到这些缺点,同时 symfony 又要充分利用配置文件的优点。事实上,symfony 的配置系统的目标是: · 强大:所有的可以配置的东西都可以用配置文件配置 · 简单:很多配置信息不会出现在普通的应用程序里,因为它们很少需要改 变 · 容易:开发者可以很容易的阅读,建立,修改配置文件 · 可定制:默认的配置语言是 YAML,但也可以换成 INI、XML,或者是别的 格式 · 快速:应用程序本身不用处理配置文件,由配置系统处理配置文件,把配 置文件编译成能够快速执行的 PHP 代码 YAML 语法与 symfony 惯例 symfony 默认使用 YAML 格式存放配置信息,而不用传统的 INI 或者 XML 格式。 YAML 通过缩进表示结构而且写起来很快。它的特点与基本规则在第一章里面已 经讲到了。不过,在写 YAML 的时候,你还需要记住几条规则。本节将介绍最常 用的几个规则。想完整的了解 YAML,请访问 YAML 网站(http://www.yaml.org/)。 首先,绝不要在 YAML 文件里使用制表符(tab),应该使用空格。YAML 解析器不 能解析制表符,所以请使用空格来缩进(在 symfony 里面使用两个空格缩进),如 例 5-1。 例 5-1 - YAML 文件禁用制表符(tab) # 绝不用制表符 all: -> mail: -> -> webmaster: webmaster@example.com # 应该使用空格 all: mail: webmaster: webmaster@example.com 如果你的参数是以空格开始或者结束的字符串,应使用单引号把它包起来。如果 一个字符串参数包含特殊字符,也要用单引号包起来,如例 5-2。 例 5-2 - 非标准的字符串要用单引号包起来 错误 1: This field is compulsory 错误 2: ' This field is compulsory ' 错误 3: 'Don''t leave this field blank' # 必须用两个单引号来表示字符 串中的' 利用特殊字符头(> 或 |)与一个缩进,长的字符串可以跨行表示。如例 5-3。 例 5-3 - 定义长的多行字符串 accomplishment: > # 由>开头的折叠式 Mark set a major league # 每一个换行被折叠成一个空格 home run record in 1998. # 使得 YAML 可读性更强 stats: | # 由|开头的原始式 65 Home Runs # 所有的换行都被保留 0.278 Batting Average # 缩进不会在结果里出现 如果要定义数组,需要用方括号把元素括起来或者使用展开的减号语法,如例 5-4。 例 5-4 - YAML 的数组语法 # 数组语法的简写 players: [ Mark McGwire, Sammy Sosa, Ken Griffey ] # 数组的展开语法 players: - Mark McGwire - Sammy Sosa - Ken Griffey 如果要定义关联数组或者说哈希表,要用大括号把元素括起来,键与值 key: value 中间保留一个空格。也可以用展开语法,每一个新的键增加一个缩进与换 行,如例 5-5。 例 5-5 - YAML 关联数组语法 # 错误的语法,冒号后缺少空格 mail: {webmaster:webmaster@example.com,contact:contact@example.com} # 关联数组的正确简写 mail: { webmaster: webmaster@example.com, contact: contact@example.com } # 关联数组的展开语法 mail: webmaster: webmaster@example.com contact: contact@example.com 定义一个布尔值时,on、1 或者 true 代表肯定值,off、0、或者 false 代表否 定值。如例 5-6。 例 5-6 - YAML 布尔值语法 true_values: [ on, 1, true ] false_values: [ off, 0, false ] 请不要吝啬使用注释(以井号#开头)还有空格,这会使注释文件更易读,如例 5-7。 例 5-7 - YAML 注释语法与值对齐 # 这是一个注释 mail: webmaster: webmaster@example.com contact: contact@example.com admin: admin@example.com # 多的空格可以帮助对齐 在某些 symfony 配置文件里面,你会发现一些行以#开头(YAML 解析器会忽略这 些行),但这些行看上去像普通的设置行。这是一个 symfony 的惯例:默认设置, 从 symfony 内核里其他的 YAML 文件里面继承的设置,这些会在你的应用程序配 置文件里面出现并用#注释起来,给你参考。如果你想改变这些参数,只要把注 释去掉就可以了。如例 5-8。 例 5-8 - 注释里的默认配置 # 缓存的默认值是关闭 settings: # cache: off # 如果你想修改这个值,去掉注释 settings: cache: on symfony 有时会把一些参数定义分类。一个分类里面的所有设置都放在分类头下 面。把长的 key: value 列表分组能增强可读性。分类头以点(.)开头。如例 5-9。 例 5-9 - 分类头与键类似,但是以.开头 all: .general: tax: 19.6 mail: webmaster: webmaster@example.com 在这个例子里,mail 是一个键,general 只是一个分类头。分类头可以当作不存 在,如例 5-10。tax 参数实际上是 all 键的直接子元素。 例 5-10 - 分类头只用于增强可读性,实际上可以忽略 all: tax: 19.6 mail: webmaster: webmaster@example.com SIDEBAR 如果你不喜欢 YAML YAML 只是一个给 PHP 代码定义设置的界面,所以 YAML 里面定义的配置信息都会 被转换成 PHP 代码。浏览一个应用程序,查看他的缓存的配置信息(例,在 cache/myapp/dev/config/)。你会发现 YAML 配置对应的 PHP 文件。这一章后面 我们会详细介绍配置缓存。 好消息是如果你不喜欢 YAML 文件,你可以自己动手,使用 PHP 代码或者其他的 格式(XML,INI 等)。在本书中,你会遇到其他的不使用 YAML 定义配置的方法, 在第 19 章你会了解如何替换 symfony 的配置文件处理器。如果你用好它们,你 可以利用这些技巧绕开配置文件或者定义你自己的格式。 救命,YAML 文件把我的程序搞死了 YAML 文件会被解析成 PHP 哈希与数组,然后这些值会在程序的不同地方改变视 图,控制器或者模型的行为。很多时候,配置文件里面的问题直到用到的时候才 被察觉。而且,显示的错误信息通常不是明显与 YAML 配置文件相关的。 如果改变了配置文件之后程序突然停止运行了,应 该 检查一下你是否犯了下面的 常见的 YAML 错误: · 键和值中间缺少空格: key1:value1 # 冒号:后缺少空格 · 数组里面的键应该按照同样的方式缩进: all: key1: value1 key2: value2 # 缩进与其他的数组元素不同 key3: value3 · 键或值里有 YAML 保留字符而且没有用单引号: message: tell him: go way # :, [, ], { and } 是 YAML 保留字符 message: 'tell him: go way' # 正确的语法 · 修改一个被注释了的行: # key: value # 由于前面的#,这行永远不会生效 · 同一级别相同键的值设置了两次: key1: value1 key2: value2 key1: value3 # key1 定义了两次,取最后一次定义的值 · 你认为设置值应该是一个特定的类型,实际上如果你不转换它,设置值永 远是字符串: income: 12,345 # 除非你转换它,否则它永远是字符串 配置文件概述 配置信息按照目的存放在不同的文件里。这些文件包含参数定义或者设置。一些 参数可以在不同的级别覆盖(项目,应用程序,模块);一些只针对特定级别。本 节将介绍这些配置文件,第 19 章将更深入的介绍配置文件。 项目配置 symfony 项目有一些默认配置文件。下面是 myproject/config/目录下面的配置 文件: · config.php:这是所有页面或者命令行方式 PHP 脚本执行的第一个文件。 它包含 symfony 框架的路径,你可以把这个路径指定到另外一个 symfony 框架。如果你在这里增加一些 define 语句,这样定义的常量在项目的每 个应用程序都可以访问得到。第 19 章将会深入介绍这个文件的使用。 · databases.yml:这个文件用来存放数据库连接设置(主机,登录名,密码 等)。第 8 章有更详细的介绍。这个文件的配置信息可以在应用程序这一 级别覆盖。 · properties.ini:这个文件存放命令行工具需要的参数,包括项目名称, 远程服务器的连接设置。第 16 章将详细介绍此文件的功能。 · rsync_exclude.txt:这个文件定义同步服务器的时候哪些文件不需要同 步。详见第 16 章。 · schema.yml 与 propel.ini:这两个文件是 Propel(symfony 的 ORM 层) 的数据访问配置文件。它们用来使 Propel 与 symfony 的类还有项目的数 据协同工作。propel.ini 是自动生成的,所以你不用修改它。如果你不 用 Propel,就不需要这些文件。这两个文件的使用详见第 8 章。 这些文件多数时候是被外部组件或者命令行使用,或者 symfony 在 YAML 解析程 序载入之前就要用到它们。所以有些文件不是 YAML 格式。 应用程序配置 配置的主要部分是应用程序配置。前端控制器(在 web/目录)里定义主要的常量, YAML 文件在应用程序目录的 config/目录里,i18n/目录里是国际化需要的文件, 还有一些在框架文件里隐藏着的但是很有用的其他项目配置信息。 前端控制器配置 前端控制器里存放了应用程序最开始的配置信息;它是一个请求最开始执行的脚 本。请看例 5-11 中默认的 web/index.php。 例 5-11 - 默认的生产环境前端控制器 getController()->dispatch(); 定义了应用程序名(myapp)与环境(prod)之后,先载入通用配置文件,然后继续 分派请求。这里定义了一些有用的常量: · SF_ROOT_DIR:项目根目录 (一般情况请保留默认值,除非想变更目录结 构)。 · SF_APP:项目中的应用程序名。需要它来生成文件路径。 · SF_ENVIRONMENT:环境名 (prod, dev, 或者其他你定义的本项目的环 境)。用来决定使用哪一套配置信息。本章稍后会解释环境的概念。 · SF_DEBUG:是否启用调试模式 (详见第 16 章)。 如果你要改变这些值,那么你可能需要另一个前端控制器。下一章将介绍前端控 制器以及如何新建一个前端控制器。 SIDEBAR 根目录可以在任何地方 只有 web 根目录(symfony 项目的 web/目录)里的脚本对外界公开。前端控制器脚 本,图片,样式表,还有 JavaScript 文件是公开的。其他文件必须放在服务器 web 根目录之外--也就是说其他任何地方。 前端控制器通过 SF_ROOT_DIR 这个路径访问项目的非公开的文件。一 般 来 说 ,项 目 根 目 录 就 是 web/目录的上一层目录。但是你可以选择一个完全不同的文件结 构。假设你的主目录结构由两个目录组成,一个公开另外一个私有: symfony/ # 私有区域 apps/ batch/ cache/ ... www/ # 公开区域 images/ css/ js/ index.php 在这种情况下,项目根目录是 symfony/目录。所以 index.php 前端控制器只要 这样定义整个应用程序就可以工作了: define('SF_ROOT_DIR', dirname(__FILE__).'/../symfony'); 第 19 章将会告诉你更多如何修改 symfony 来实现特殊的目录结构的信息。 主应用程序配置 主应用程序配置存放在 myproject/apps/myapp/config/目录下的文件里: · app.yml:这个文件存放应用程序相关的配置信息,包括定义业务或者程 序逻辑的全局变量,这些都不需要存放在数据库里。税率,运费,e-mail 地址等经常存放在这个文件。这个文件默认是空的。 · config.php:这个文件引导应用程序,它会做所有的基础初始化来启动应 用程序。这里你可以定制目录结构或者增加应用程序相关的常量(详见第 19 章)。他首先会包含项目的 config.php 文件。 · factories.yml:symfony 在这里定义处理视图、请求、回应、会 话 (session) 等的类。如果你想用你自己的类取代 symfony 的类,你可以在这里定义它 们。详见第 19 章。 · filters.yml:过滤器是在每个请求都被执行的一小段代码。这个文件用 来定义哪些过滤器需要被执行,每个模块都可以改写过滤器配置。详见第 6 章。 · logging.yml:这个文件定义哪些情况需要被记录到日志里,从而管理与 调试应用程序。详见第 16 章。 · routing.yml:路由规则,能把难以理解的不好记忆的 URL 变成"漂亮"的 直观形式。此配置文件用来存放这些信息。每个新应用程序都会有一些默 认路由规则。详见第 9 章。 · settings.yml:这个文件存放 symfony 应用程序的主要配置信息。你的应 用程序是否使用国际化功能,它的默认语言,请求 timeout 时间,是否开 启缓存功能等都在这个文件里面定义。只要改变这个文件里的一行代码, 你就可以关闭网站来执行维护升级。这些设置还有它们的用法详见第 19 章。 · view.yml:这个文件里定义默认视图的结构(布局的名称,标题,还 有 meta tag;默认载入的样式表及 Javascript;默认的 content-type 等)。还有 默认的 meta 与标题标签。详见第 7 章。这些配置信息可以在模块里改写。 国际化配置 国际化的应用程序可以显示多种语言。这需要特殊的配置。国际化配置信息存放 在如下两个地方: · 应用程序 config/目录里的 i18n.yml:这个文件定义一般的翻译设置,例 如原文的语言、在数据库还是文件里面存放翻译信息还有翻译信息的格式 等。 · 应用程序 i18n/目录里的翻译文件:这些文件基本上是字典,里面包含程 序模板里面出现的所有文字的翻译,这 样 切 换 语 言 的地方就会显示对应的 翻译。 注意开启 i18n(国际化)功能需要在 setting.yml 文件里面设置。详见第 13 章。 其它应用程序配置 还有一部分配置文件在 symfony 的安装目录里(在 $sf_symfony_data_dir/config/),这些文件在所有的项目配置目录里都找不到。 这是一些很少需要修改或者对于全部项目共用的配置信息。不过,如果你需要改 动它们,只要在你的 myproject/apps/myapp/config/里建立一个相同名字的空 文件,然后在里面改写你要修改的配置信息就可以了。应用程序里的配置信息总 是优先于框架的配置信息。下面是 symfony 安装目录的 config/目录里的文件: · autoload.yml;此文件包含了自动载入功能的配置信息。这个功能帮你从 特定的目录自动载入你写的类。详见第 19 章。 · constants.php:此文件包含了默认的应用程序文件结构。请使用应用程 序的 config.php 来覆盖这些配置信息。详见第 19 章。 · core_compile.yml 和 bootstrap_compile.yml:这两个文件记录启动应 用程序(在 bootstrap_compile.yml 里)和处理请求(在 core_compile.yml 里)需要载入哪些类。这些类被压缩在一个没有注释的 PHP 文件里,这样 可以最小化文件处理从而加快执行速度(每个请求只载入 1 个文件而不是 40 多个文件)。这在没有安装 PHP 加速器的时候特别有效。优化技术详见 第 18 章。 · config_handlers.yml:在这个文件里你可以增加或者修改处理配置文件 的工具。详见第 19 章。 · php.yml:这个文件用来检查 php.ini 里的配置信息是否满足程序需要, 并且可以帮你覆盖 php.ini 的配置。详见第 19 章。 模块配置 默认情况下,模块没有特别的配置信息。不过,如果你需要,你可以为某个模块 覆盖应用程序级的配置。例如,你需要修改一个模块里所有动作的 HTML 描述信 息,或是载入一个特定的 Javascript 文件。你可以选择针对某个特定的模块增 加新参数来实现保护性封装。 可能你已经猜到,模块配置文件必须放在 myproject/apps/myapp/modules/mymodule/config/目录里,模块配置信息有下 面这几个文件: · generator.yml:根据数据库表自动生成的模块(脚手架与管理后台)会用 这个文件, 它用来定义界面怎么显示行和列,用户可以执行哪些操作(过 滤器,排序,按钮等)。详见第 14 章。 · module.yml:这个文件包含模块的特殊参数(相当于 app.yml,但这是模 块级的),还有动作的配置信息。详见第 6 章。 · security.yml:这个文件用来给动作设置访问限制。你可以在这里设置哪 个页面只能给注册用户看或是一部分有特殊权限的注册用户看。详见第 6 章。 · view.yml:这个文件包含模块的一个或者所有动作的视图配置信息。它会 覆盖应用程序级的 view.yml,详见第 7 章。 · 数据验证文件:虽然这些用来验证表单输入数据的 YAML 数据验证文件存 放在 validate/目录而不是 config/目录里,它 们 仍 然 属 于 模 块 配置文件。 详见第 10 章。 大多数模块配置文件能让你为一个模块的所有视图或者动作定义参数,也可以只 为模块里的一部份视图或者动作定义参数。 SIDEBAR 文件太多了? 可能应用程序里的配置文件太多了,使你受到了打击。不过请注意: 大多数时候你都不用修改配置,因为默认的配置就能够满足大部分的需求。每个 配置文件与一个特定的功能相关,以后的章节会一个一个详细介绍它们的用法。 当你关注某一个配置文件的时候,你就能清楚的了解它的用途还有它的组织形 式。对于专业 web 开发,默认的配置不会经常被完全重写。配置文件使你不用修 改一行代码轻易地改变 symfony 的功能。想想看要达到同样的目的需要多少 PHP 代码吧。如果所有的配置文件都存放在同一个文件里,那么这个文件不仅仅会变 得完全无法阅读,同样你也无法在不同的级别重新定义配置信息(请看本章后面 的"配置层叠"小节)。 配置系统是 symfony 重大优点之一,它使 symfony 适合于 几乎所有类型的 web 应用程序,不仅限于这个框架原本的设计目的。 环境 在开发应用程序的过程中,你可能同时需要好几套配置信息。例如,开发过程中 用来测试的数据库配置信息,还有生产环境中正式的数据库配置信息。symfony 为满足同时使用不同配置信息的需求,提供了不同的环境。 什么是环境? 一个应用程序可以在不同的环境中运行。不同的环境共享相同的 PHP 代码(前端 控制器除外),但是配置信息可能完全不同。symfony 为每个应用程序提供三种 默认的环境:生产(prod)、测试(test)和开发(dev)。只要你愿意你可以无限制 的增加环境的数量。 所以基本上,环境与配置是同义词。例如,测试环境会记录警告与错误,生产(prod) 环境只记录错误。缓存加速功能在开发(dev)环境下通常是关闭的,但是在测试 (test)与生产(prod)环境下开启。开发与测试环境所需要的测试数据,存放在与 生产环境不同的数据库里。所以两种环境的数据库配置会有所不同。所有的环境 可以在一台机器上共存,不过通常一台正式的服务器只包含生产(prod)环境。 在开发环境下,日志与调试功能都是开启的,因为排除问题比性能更重要。相反, 生产环境的配置默认是为性能优化的,所以生产环境会关闭一些功能。建议呆在 开发环境直到你对开发的功能满意为止,然后切换到生产环境测试速度。 测试环境又与开发与生产环境有所不同。你只能通过命令行来访问这个环境,通 常 是做功能测试和执行批处理脚本。因此,测试环境与生产环境接近,但是不能 通过浏览器访问。它能模拟 coookie 与其它 HTTP 相关的组件。 改变你正在访问的应用程序的环境,只要改变前端控制器就可以了。到目前为止, 你只见过开发环境,因为例子里的 URL 都是开发环境前端控制器的: http://localhost/myapp_dev.php/mymodule/index 不过,如果你想看看应用程序在生产环境中的样子,可以执行生产环境的前端控 制器: http://localhost/index.php/mymodule/index 如果你的 web 服务器支持 mod_rewrite,你甚至可以在 web/.htaccess 里自定义 symfony 重写规则。这些规则把生产环境的前端控制器作为默认的执行脚本能让 URL 看上去像这样: http://localhost/mymodule/index SIDEBAR 环境与服务器 不要把环境与服务器的概念混淆了。在 symfony 里,不同的环境是指不同的配置 信息,对应不同的前端控制器(执行请求的脚本)。不同的服务器对应 URL 里的不 同域名。 http://localhost/myapp_dev.php/mymodule/index _________ _____________ 服务器 环境 通常,开发人员在一台开发服务器上工作,这台服务器不与互联网相连,所有的 服务与 PHP 配置文件都可以自由修改。到了发布应用程序的时候,程序文件会传 到生产服务器上然后最终用户才能访问得到。 这意味着同一台服务器上可以有很多个环境。例如,你甚至可以在你的开发服务 器上运行生产环境。不过,大多数时候,生产服务器上只能访问生产环境,这样 可以避免服务器的配置信息泄露以减少安全方面的风险。 定义一个新环境,不需要建立目录或者使用 symfony 命令行工具。只要建立一个 新的前端控制器,修改这个前端控制器里面定义的环境名就可以了。这个环境会 继承所有默认配置信息和所有环境的共同配置信息。下一章会有详细介绍。 配置层叠 同样的配置信息可以在不同的地方定义多次。例如,你想把你的程序的所有页面 的 mime-type 定义成 text/html,只有一个 rss 模块例外,这个模块需要 text/xml 的 mime-type。symfony 可以让你在 myapp/config/view.yml 里面写第一个定义, 然后在 myapp/modules/rss/config/view.yml 里面写第二个定义。配置系统了解 模块级别的定义一定要覆盖应用程序级的定义。 实际上,symfony 具有如下的配置级别: · 粒度级别: o 框架里的默认配置 o 整个项目的全局配置 (在 myproject/config/ 里) o 项目中应用程序的配置 (在 myproject/apps/myapp/config/ 里) o 模块的配置 (在 myproject/apps/myapp/modules/mymodule/config/ 里) · 环境级别: o 针对某一个环境 o 所有环境 所有可以自定义的属性里,有一些是与环境有关的。因此,很多 YAML 配置文件 是按照环境分成了好几段,最后一段针对所有环境。因此一个典型的 symfony 配置文件类似于例 5-12。 例 5-12 - symfony 配置文件的结构 # 生产环境设置 prod: ... # 开发环境设置 dev: ... # 测试环境设置 test: ... # 自定义环境设置 myenv: ... # 所有环境的设置 all: ... 另外,symfony 框架本身定义的默认值并不在项目的目录里,它 们 在 你 的 symfony 的$sf_syfmony_data_dir/config/目录里。默认的配置信息在例 5-13 的文件里 设置。所有的应用程序都会继承到这些设置。 例 5-13 - 默认配置信息, 在 $sf_symfony_data_dir/config/settings.yml 里 # Default settings: default: default_module: default default_action: index ... 这些配置会在项目,应用程序,模块的配置信息里面以注释的形式反复出现,如 例 5-14 所示,这样你就可以知道这些默认值并且可以修改它们。 例 5-14 - 默认配置信息, 在 myapp/config/settings.yml 中出现以供参考 #all: # default_module: default # default_action: index ... 这意味着一个属性可以多次定义,最后的取值取决于层叠的结构。任何一个特定 环境里定义的参数优先于所有环境里定义的参数,所有环境里的参数优先于默认 配置。模 块 配置里的参数优先于应用程序级里定义的同样参数,应用程序里的参 数优先于项目级。这可以通过下面的优先级列表来表示: 1. 模块 2. 应用程序 3. 项目 4. 特定的环境 5. 所有环境 6. 默认 配置缓存 执行时解析 YAML 处理配置文件的层叠结构会增加每次请求的负担。symfony 内 建了配置文件缓存机制来提高请求速度。 不管什么格式的配置文件都需要一些特别的类来处理,又叫处理器,这些配置文 件被转换成快速执行的 PHP 代码。开发环境里,处理着每次请求都会去检查配置 文件的变化,这样提高交互性。它们解析改变的配置文件使你能马上看到 YAML 改变的效果。但是在生产环境,这样的处理只在第一次请求时进行,处理得到的 PHP 代码被保存下来给后面的请求使用。这样能提高性能,因为生产环境里的每 次请求只需要执行一些优化过的 PHP 代码。 例如,如果 app.yml 文件内容如下: all: # 所有环境的设置 mail: webmaster: webmaster@example.com 那么 cache/目录下的 config_app.yml.php 文件,将包括下面的内容: 'webmaster@example.com', )); 这样,大多数时候,symfony 不需要去解析 YAML 文件,只需要执行 cache 里的 配置信息就可以了。不过在开发环境,symfony 会自动比较 YAML 文件还有配置缓 存的修改时间,只重新处理上次请求后修改过的配置文件。 这是 symfony 与其他很多 PHP 框架相比的一个主要优势,这些 PHP 框架里的每次 请求都会去处理配置文件,即使是生产环境。与 Java 不同,PHP 不会在请求之 间共享执行状态。 其他依赖 XML 配置文件的框架在每次请求时处理 XML 性能损 失很大。symfony 不存在这个问题,配置文件带来的速度影响很小。 这样的机制带来一个重要的问题,如果你改变了生产环境的配置信息,你需要强 制重新解析所有你修改过的配置文件,这 样改变才能生效。你只需要清除缓存就 可以了,可以直接清空 cache/目录的内容,或是执行 clear-cache 这个 symfony 任务: > symfony clear-cache 从代码里访问配置信息 所有的配置文件最终都被转换成 PHP 代码,框架会自动使用很多设置。不过,有 时你需要在你的代码中(动作,模板,自定义类等)访问配置文件里定义的设置。 settings.yml、apps.yml、module.yml、logging.yml 还有 i18n.yml 里的配置 信息可以通过一个特殊的 sfConfig 类来访问。 sfConfig 类 你可以在程序代码里通过 sfConfig 类访问配置信息。它是一个配置信息的登记 处,它有一些简单的存取方法,这些存取方法可以在程序的任何地方使用。 // 取得一个设置 parameter = sfConfig::get('param_name', $default_value); 注意你也可以在 PHP 代码里定义或者覆盖一个设置: // 定义一个设置 sfConfig::set('param_name', $value); 参数的名字由几部分组成,中间用下划线分割,顺序如下: · 与配置文件名有关的前缀 (sf_ 代表 settings.yml, app_ 代表 app.yml, mod_ 代表 module.yml, sf_i18n_ 代表 i18n.yml, sf_logging_ 代表 logging.yml) · 父键名 (如果有), 小写形式 · 键名, 小写形式 参数名字不包括环境名称,因为 PHP 代码只能访问到执行时所在的环境里定义的 参数。 例如,如果你需要访问 app.yml 里定义的值,见例 5-15,你需要例 5-16 中的代 码。 例 5-15 - app.yml 配置文件样本 all: version: 1.5 .general: tax: 19.6 default_user: name: John Doe mail: webmaster: webmaster@example.com contact: contact@example.com dev: mail: webmaster: dummy@example.com contact: dummy@example.com 例 5-16 - 在 dev 环境从 PHP 代码里访问配置信息 echo sfConfig::get('app_version'); => '1.5' echo sfConfig::get('app_tax'); // 请注意分类的头会被忽略掉 => '19.6' echo sfConfig::get('app_default_user_name); => 'John Doe' echo sfConfig::get('app_mail_webmaster'); => 'dummy@example.com' echo sfConfig::get('app_mail_contact'); => 'dummy@example.com' 所以 symfony 的配置信息有所有 PHP 常量的优点,但是没有 PHP 常量的缺点,因 为 symfony 配置的值可以改变。 所以, 用来给应用程序设置框架设置的 settings.yml 文件,相当于一系列的 sfConfig::set()调用。例 5-17 会被解释为 例 5-18。 例 5-17 - 不完整的 settings.yml all: .settings: available: on path_info_array: SERVER path_info_key: PATH_INFO url_format: PATH 例 5-18 - symfony 处理 settings.yml 文件的结果 sfConfig::add(array( 'sf_available' => true, 'sf_path_info_array' => 'SERVER', 'sf_path_info_key' => 'PATH_INFO', 'sf_url_format' => 'PATH', )); settings.yml 文件里面设置的含义请参考第 19 章。 自定义应用程序配置与 app.yml 大部分与程序功能有关的设置存放在 app.yml 文件里,这个文件在 myproject/apps/myapp/config/目录。app.yml 与环境有关,默认是空的。把所 有你需要很容易修改的设置放在这个文件里,在代码里用 sfConfig 类访问它们。 如例 5-19。 例 5-19 - 这个 app.yml 给指定的网站定义接受的信用卡类型 all: creditcards: fake: off visa: on americanexpress: on dev: creditcards: fake: on 想要知道当前环境是否接受 fake 信用卡,需要这么写代码: sfConfig::get('app_creditcards_fake'); TIP 当你要定义一个常量或者一个设置的时候,考虑一下把它放在 app.yml 里面 会不会更好。在这里存放应用程序配置很方便。 如果你的自定义参数用 app.yml 的语法难以处理,你可以考虑自己定义一套语 法。这样你可以把配置信息存在一个新的文件里,用新的处理器解析配置文件。 配置文件处理器的资料详见第 19 章。 更多使用配置文件的技巧 在开始写你自己的 YAML 文件之前,有一些最后的技巧需要掌握。这些技巧可以 避免配置信息的重复及其如何处理你自己的 YAML 格式。 在 YAML 文件里使用常量 一些配置设置的值取决于其他的设置。为了避免重复设置同样的值,symfony 支 持在 YAML 文件里使用常量。如果遇到%包起来的大写形式的设置名(可以通过 sfConfig::get()取得值),配置文件处理器会用这个设置的当前值来替换这个常 量。见例 5-20。 例 5-20 - 在 YAML 文件里使用常量,以 autoload.yml 为例 autoload: symfony: name: symfony path: %SF_SYMFONY_LIB_DIR% recursive: on exclude: [vendor] path 参数的值会是执行 sfConfig::get('sf_symfony_lib_dir')的结果。如果一 个配置文件依赖于另一个配置文件,被依赖的配置文件必须先被解析(请查看 symfony 的源代码来了解配置文件载入的顺序)。app.yml 是最后被解析的文件之 一,所以你可以在这个文件里使用其它文件里定义的设置。 在配置文件里使用脚本 有可能你的配置信息与外部参数有关(例如数据库或者其他配置文件)。为了解决 这种问题,symfony 在把配置文件传给 YAML 处理器之前先用 PHP 来解析配置文 件。这意味着你可以在 YAML 文件里使用 PHP 代码,如例 5-21。 例 5-21 - YAML 文件可以包含 PHP all: translation: format: 但是请注意配置文件在请求周期早期就被处理了,所以你不能使用 symfony 内建 的方法或者函数。 CAUTION 在生产环境中,配置文件会被缓存,所以配置文件只会在清除缓存后被 处理(并执行)一次。 浏览你的 YAML 文件 如果你想直接读取 YAML 文件,你可以使用 sfYaml 类。它是一个可以把 YAML 文 件转化成 PHP 数组的 YAML 解析器。例 5-22 是一个 YAML 文件的例子,例 5-23 是前面 YAML 文件的解析结果。 例 5-22 - test.yml house: family: name: Doe parents: [John, Jane] children: [Paul, Mark, Simone] address: number: 34 street: Main Street city: Nowheretown zipcode: 12345 例 5-23 - 使用 sfYaml 类把 YAML 转换成一个数组 $test = sfYaml::load('/path/to/test.yml'); print_r($test); Array( [house] => Array( [family] => Array( [name] => Doe [parents] => Array( [0] => John [1] => Jane ) [children] => Array( [0] => Paul [1] => Mark [2] => Simone ) ) [address] => Array( [number] => 34 [street] => Main Street [city] => Nowheretown [zipcode] => 12345 ) ) ) 总结 symfony 配置系统使用的 YAML 语言简单并且可读性强。多种环境与层叠结构的 参数定义为开发者提供了多种选择。一些配置文件可以从代码里通过 sfConfig 类访问,特别是 app.yml 里的应用程序配置。 的确,symfony 有很多配置文件,不过这使 symfony 适用性更强。注意除非你的 应用程序需要高级别的定制,否则你根本不需要去修改它们。 第 6 章 深入了解控制器层 在 symfony 中, 控制器是连接商业逻辑和视图层的程序。 根据不同的功能,它 又被细分为几个小部分: · 前端控制器是应用程序的唯一入口。 它载入配置文件并且指定将被执行 的动作。 · 动作包含应用逻辑。它们首先确定请求的完整性, 然后为表现层准备好 数据。 · 通过请求,应答,session 对象,我们可以获取请求参数,应答 HTTP 头, 和持久性的用户数据。这些数据经常被用在控制器层。 · 每一个请求都要执行过滤器程序,这个程序将在动作的前后执行。 例如, 安全和确认过滤器是网络编程经常要用到的。 你可以延伸架构去创作自 己的过滤器。 这一章将介绍这些组件。不要担心。一个简单的页面,你只需要在动作的类里面 写几行代码而已。其他的控制器层组件仅仅被用于一些特定的情况。 前端控制器 前端控制器接收并且处理所有的请求。所以前端控制器是整个应用程序的唯一入 口。 当前端控制器接收到一个请求, 路由选择系统将根据用户所用的 URL 连接相应 的动作和模块。例如,下列的 URL 调用 index.php 脚本(这是前端控制器),它可 以被理解为调用 mymodule 模块里的 myAction 动作: http://localhost/index.php/mymodule/myAction 如果你对 symfony 的内部结构没有兴趣, 以上对前端控制器的认识已经足够了。 它是 symfony MVC 架构中不可缺少的组件, 但是你很少去更改它。 你可以跳过 下一节,除非你想更深入地了解前端控制器的结构。 前端控制器的工作细节 前端控制器处理请求的时候不仅仅是分配将需要执行的动作。其实,它还执行所 有动作的公共代码,它们包括: 1. 定义核心常量。 2. 定位 symfony 程序库。 3. 调入并初始化核心架构的类。 4. 调入配置文件。 5. 处理请求的 URL 并且指定将被执行的动作和请求参数。 6. 如果动作不存在, 转发给专门接收 404 错误信息的动作。 7. 启动过滤器 (例如,如果请求需要被认证)。 8. 执行过滤器, 第一回。 9. 执行所需要的动作并将结果提交给视图。 10.执行过滤器, 第二回。 11.输出应答。 默认的前端控制器 index.php 是默认的前端控制器。它是一个非常简单的 PHP 文件,在 web/ 文件 夹里。请看例 6-1。 例 6-1 - 默认的前端控制器 getController()->dispatch(); 首先是上一节讲的第一步,定 义 核心常量。然后前端控制器调入 config.php, 也 就是上面的第二步到第四步。 执行 sfController 对象(它是 symfony MVC 架构 里的核心控制器对象)中的 dispatch()函数处理请求,也就是上面的第五步到 第七步。 最后一步是执行过滤器。这部分稍后再讲解。 调用其他的前端控制器来切换环境 一个环境只能有一个前端控制器。事实上,我们应该说一个前端控制器确定了一 个环境。 SF_ENVIRONMENT 常量是环境的定义。 如果你想在项目中切换另一个环境,只需要选择另一个前端控制器。当你用 symfony init-app 创建一个新项目时,在生产环境中默认的前端控制器是 index.php。而在开发环境中默认的前端控制器是 myapp_dev.php(如果你的项 目叫 myapp)。如果 URL 没有指明前端控制器文件名,mod_rewrite 将用 index.php 作为默认值。所以下面这两个 url 在生产环境中是相同的(mymodule/index)。 http://localhost/index.php/mymodule/index http://localhost/mymodule/index 与此页面相同的开发环境的 URL 为: http://localhost/myapp_dev.php/mymodule/index 建立一个新的环境非常容易,只需要增添一个新的前端控制器。比如你想让客户 检验你的项目,你可以建立一个展示环境。你只需要拷贝一份 web/myapp_dev.php, 命名为 web/myapp_staging.php。然后把常量 SF_ENVIRONMENT 改为 staging。 最后,在所有的配置文件里添加 staging:部 分并赋值, 请参看例 6-2。 例 6-2 - 样本 app.yml,展示(staging)环境的设置 staging: mail: webmaster: dummy@mysite.com contact: dummy@mysite.com all: mail: webmaster: webmaster@mysite.com contact: contact@mysite.com 如果你想看一下新环境带来的变化,调用相应的前端控制器: http://localhost/myapp_staging.php/mymodule/index 批处理文件 如果你想在命令行或 crontab 上执行一个脚本并能享用 symfony 的类和其他功 能,比如发出大批电子邮件或通过大量计算而定时地更新你的模型。对于这种脚 本,你需要包括前端控制器里的前几行代码。例 6-3 显示批处理脚本文件的开头 部分。 例 6-3 - 批处理脚本文件样本 关闭标签。因为关闭标签不是必须 的,而且如果在关闭标签之后包含空格会造成问题。 另外,如果你特别注意,你会发现 symfony 程序的行从来不会以空格结尾。原因 就很简单:因为在 Fabien 的编辑器里空格结尾的行看起来很丑。 另一种动作类语法 还有一个方法是把动作分开,一个文件只有一个动作。在这种情况下,每一个动 作类扩展 sfAction (而不是 sfActions)并且命名为 actionNameAction。动 作被命名为 execute。文件名和类名相同。所以例 6-5 可以分成两个文件,例 6-6 和 6-7。 例 6-6 - 单个动作文件, myapp/modules/mymodule/actions/indexAction.class.php class indexAction extends sfAction { public function execute() { ... } } 例 6-7 - 单个动作文件, myapp/modules/mymodule/actions/listAction.class.php class listAction extends sfAction { public function execute() { ... } } 在动作里获取信息 在动作类里你可以获取控制器相关的信息和 symfony 的核心对象。例 6-8 展示它 的用法 例 6-8 - sfActions 常用方法 class mymoduleActions extends sfActions { public function executeIndex() { // Retrieving request parameters $password = $this->getRequestParameter('password'); // Retrieving controller information $moduleName = $this->getModuleName(); $actionName = $this->getActionName(); // Retrieving framework core objects $request = $this->getRequest(); $userSession = $this->getUser(); $response = $this->getResponse(); $controller = $this->getController(); $context = $this->getContext(); // Setting action variables to pass information to the template $this->setVar('foo', 'bar'); $this->foo = 'bar'; // Shorter version } } SIDEBAR 环境单例(context singleton) 你已经看到了,在前端控制器里,有一个 sfContext::getInstance()调用。在 动作里,getContext()方法也会返回同样的单例。这个很有用的对象保存了与某 个请求有关的所有 symfony 核心对象,并且为它们提供了读取方法: sfController: 控制器对象(->getController()) sfRequest: 请求对象 (->getRequest()) sfResponse: 应答对象 (->getResponse()) sfUser: 用户 session 对象 (->getUser()) sfDatabaseConnection: 数据库链接对象 (->getDatabaseConnection()) sfLogger: 日志对象 (->getLogger()) sfI18N: 国际化对象 (->getI18N()) 可以在代码的任何地方调用 sfContext::getInstance()。 动作结束 在动作结束前有几种状况。动作返回的数据将决定如何显示视图。在 sfView 类 里的常量决定哪一个模板被用于展示动作的结果。 如果有一个默认的视图(最普遍的情况), 动作的结尾应该是这样的。 return sfView::SUCCESS; symfony 将寻找 actionNameSuccess.php 模板。这是动作的默认方式,所以即使 你省略了 return 这一行,symfony 一样会寻找并使用 actionNameSuccess.php 模板。即便动作是空的也一样。例 6-9 是动作结束的例子。 例 6-9 - 动作返回数据给 indexSuccess.php 和 listSuccess.php 模板 public function executeIndex() { return sfView::SUCCESS; } public function executeList() { } 如果有错误,动作应该这样结尾: return sfView::ERROR; symfony 就会去寻找 actionNameError.php 模板。 如果你想用一个特别的视图,你可以这样结尾: return 'MyResult'; symfony 就会去寻找 actionNameMyResult.php 模板。 如果根本就没有或不需要视图--例如,批处理文件的执行--应该这样结尾: return sfView::NONE; 在这种情况下,视图层就不会被执行了。这 就意味着你完全可以越过视图层直接 从动作输出 HTML 代码。请参看 6-10, symfony 提供一个 renderText()方法。这 个方法在响应速度要求很高的动作中会非常有用,比如和 Ajax 的互动。我们将 在第 11 章讨论。 例 6-10 - 用 sfView::NONE 越过视图层直接输出回应 public function executeIndex() { echo "Hello, World!"; return sfView::NONE; } // Is equivalent to public function executeIndex() { return $this->renderText("Hello, World!"); } 在一些情况下,你需要回复一个空的应答但要有 HTTP 头(特别是 X-JSONHTTP 头)。通过 sfResponse 对象定义 HTTP 头将在下一章讨论。返回 HTTP 头 sfView::HEADER_ONLY 常量, 请参看例 6-11。 例 6-11 - 避开视图层,但应答有 HTTP 头 public function executeRefresh() { $output = '<"title","My basic letter"],["name","Mr Brown">'; $this->getResponse()->setHttpHeader("X-JSON", '('.$output.')'); return sfView::HEADER_ONLY; } 如果动作需要一个特殊的模板,去掉 return 声明, 用 setTemplate()方法。 $this->setTemplate('myCustomTemplate'); 跳到另一个动作 在一些情况下,一个动作结束时需要执行另一个动作。例如,一个处理表单提交 的动作在更新数据库后通常被跳转到另一个动作上。第二个例子是动作别名:动 作 index 经常展示一个表,其实它转给了动作 list。 动作类里提供了两个方法可以执行另一个动作: · 如果动作转发给另一个动作: $this->forward('otherModule', 'index'); · 如果跳转到另一个动作: $this->redirect('otherModule/index'); $this->redirect('http://www.google.com/'); NOTE forward 与 redirect 之后的动作代码不会被执行。这一点上与 return 语 句是一样的。它们会抛出一个 sfStopException 异常来停止动作的执行; 这个 异常稍后会被 symfony 截获然后忽略。 选择转发或跳转有时并不容易。为了做出最好的选择,请记住转发在应用程序内 部进行,所以对于用户来说比较直接易懂。在用户的眼里,浏览器显示的 URL 和用户请求的 URL 是一样的。相反地,跳转是一条发给用户浏览器的消息,包括 一个新的请求并改变了最终的 URL。 如果一个表单通过 method="post"调用动作,你应该使用跳转。最大的优点是, 如果用户刷新页面,表单不会被重新提交;另外,如果用户点击后退键,浏览器 会再显示表单,而不是询问用户是否要重新提交表单。 forward404()是一个特殊并很常用的 forward 方法。它 forward 给"page not found"动作。当动作所需的请求参数不全时(比如查出一个错误的 URL),这个 方法就会被用到。例 6-12 展示的例子是一个 show 动作需要一个 id 参数。 例 6-12 - 使用 forward404()方法 public function executeShow() { $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id')); if (!$article) { $this->forward404(); } } TIP 如果要找错误 404 的动作和模板,可以在 $sf_symfony_data_dir/modules/default/目录里找到它们。可以通过在你的应 用程序里增加一个新的 default 模块来定制这个页面,覆盖框架里的内容,定 义 一个 error404 动作和 error404Success 模板就可以了。 另外,还可以修改 settings.yml 文件里的 error_404_module 和 error_404_action 常量来使用已 有的动作作为 404 页面。 经验告诉我们,在多数情况下,动作需要在做出一个判断后再转发或跳转。例如 6-12。所以 sfActions 类有几个方法叫 forwardIf(), forwardUnless(), forward404If(), forward404Unless(), redirectIf(), redirectUnless()。这 些方法接受一个参数并且对它进行判断,如果判断结果是 true,xxxIf() 方法 将被执行;如果判断结果是 false,xxxUnless() 方法将被执行。 请参看例 6-13。 例 6-13 - 使用 forward404If() 方法 // 这个动作于例 6-12 中的作用相同 public function executeShow() { $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id')); $this->forward404If(!$article); } // 这一个也是 public function executeShow() { $article = ArticlePeer::retrieveByPK($this->getRequestParameter('id')); $this->forward404Unless($article); } 这些方法不但使你的程序简短而且清晰易懂。 TIP 当动作执行 forward404()或者类似的方法的时候,symfony 会抛出 sfError404Exception 这个管理 404 回应的异常。这意味着如果在一个你不想访 问控制器的地方显示 404 错误信息,你只要抛出一个类似的异常就可以了。 几个动作共享的代码 synfony 给动作命名为 executeActionName() (如果使用 sfActions 类) 或 execute() (如果使用 sfActions 类)。这样 symfony 就能保证找到动作。你也 可以添加自己的方法, 只要你不用 execute 开头,symfony 就不会把这些方法 当做动作。 如果你需要在执行每个动作前都要执行一段代码,你可以把这段代码放入动作类 里的 preExecute()方法里。你可能猜到如果你想在执行每个动作后执行一段代 码,那你可以把这段代码放入 postExecute()方法里。 例 6-14 介绍了使用这 些方法的规则。 例 6-14 - 使用 preExecute, postExecute, 和自己定义的方法 class mymoduleActions extends sfActions { public function preExecute() { // 这里的代码会在每个动作之前执行 ... } public function executeIndex() { ... } public function executeList() { ... $this->myCustomMethod(); // 可以使用动作类里定义的方法 } public function postExecute() { // 这里的代码会在每次执行完动作之后执行 ... } protected function myCustomMethod() { // 还可以添加自己的方法,只要不以"execute"开头 // 最好把这样的方法声明成 protected 或者 private ... } } 访问请求 你或许熟悉了 getRequestParameter('myparam')方法:它被用于取得请求参数 值。事实上,这个方法只是调入 getRequest()->getParameter('myparam')的代 理方法。在动作类里可以通过 getRequest()方法访问请求对象,在 symfony 里 叫 sfWebRequest,和它所有的方法。 表格 6-1 展示一些常用的 sfWebRequest 方法。 表格 6-1 - sfWebRequest 对象里的方法 名称 功能 输出例子 请求信息 getMethod() 请求 的方 法 返回 sfRequest::GET 或者 sfRequest::POST 常量 getMethodName() 请求 方法 名 'POST' getHttpHeader('Server') 某个 指定 的 'Apache/2.0.59 (Unix) DAV/2 PHP/5.1.6' 名称 功能 输出例子 HTTP 头的 值 getCookie('foo') 某个 cooki e 的 值 'bar' isXmlHttpRequest()* 是否 是 Ajax 请 求? true isSecure() 是否 是 SSL 请 求? true 请求参数 hasParameter('foo') 请求 里是 否包 含某 个参 数? true getParameter('foo') 某个 参数 的值 'bar' getParameterHolder()->g etAll() 包含 所有 参数 的数 组 URI 相关的信息 getUri() 完整 的 URI 'http://localhost/myapp_dev.php/mymodul e/myaction' getPathInfo() 路径 信息 '/mymodule/myaction' getReferer()** 来源 'http://localhost/myapp_dev.php/' getHost() 主机 'localhost' 名称 功能 输出例子 名 getScriptName() 前端 控制 器的 路径 和名 字 'myapp_dev.php' 客户端浏览器信息 getLanguages() 所有 可接 受的 语言 组成 的数 组 Array( [0] => fr [1] => fr_FR [2] => en_US [3] => en ) getCharsets() 所有 可接 受的 字符 集组 成的 数组 Array( [0] => ISO-8859-1 [1] => UTF-8 [2] => * ) getAcceptableContentTyp e() 所有 可接 受的 内容 类型 组成 的数 组 Array( [0] => text/xml [1] => text/html *只能用于 Prototype Javascript 库 ** 有时会被代理服务器阻拦 sfActions 类里有几个代理方法可以更简捷地访问请求方法,请参看例 6-15。 例 6-15 - 从动作类里访问 sfRequest 对象方法 class mymoduleActions extends sfActions { public function executeIndex() { $hasFoo = $this->getRequest()->hasParameter('foo'); $hasFoo = $this->hasRequestParameter('foo'); // Shorter version $foo = $this->getRequest()->getParameter('foo'); $foo = $this->getRequestParameter('foo'); // Shorter version } } 如果请求有附件,sfWebRequest 对象可以访问或移动这些文件,请参看例 6-16。 例 6-16 - sfWebRequest 对象知道如何处理附件 class mymoduleActions extends sfActions { public function executeUpload() { if ($this->getRequest()->hasFiles()) { foreach ($this->getRequest()->getFileNames() as $fileName) { $fileSize = $this->getRequest()->getFileSize($fileName); $fileType = $this->getRequest()->getFileType($fileName); $fileError = $this->getRequest()->hasFileError($fileName); $uploadDir = sfConfig::get('sf_upload_dir'); $this->getRequest()->moveFile('file', $uploadDir.'/'.$fileName); } } } } 你不需要考虑服务器是否支持$_SERVER 或$_ENV 变量,默认值或服务器兼容问题 --sfWebRequest 的方法会帮你解决这些问题。另外,这些方法的名称简单易懂, 你不再需要查看复杂的 PHP 文档,寻找有关请求方面的信息了。 用户会话 symfony 会自动地处理用户会话并保持用户请求的连续性。symfony 利用 PHP 内 置的会话管理功能并增强它灵活性,使它更容易使用。 访问用户会话 在动作里,你可以通过 getUser()方法访问从 sfUser 类生成的用户会话对象。 这个类里有一个参数存储方法,可用于存储用户属性。这些存储的用户属性在用 户会话结束前是有效的。 请参看例 6-17。 用户属性可以是任何数据结构(字 符串,数组,关联数组)。每个用户都有这个功能,即使是没注册的用户。 例 6-17 - sfUser 对象可以存储用户属性 class mymoduleActions extends sfActions { public function executeFirstPage() { $nickname = $this->getRequestParameter('nickname'); // 将数据保存到用户会话 $this->getUser()->setAttribute('nickname', $nickname); } public function executeSecondPage() { // 从用户会话中取得数据,如果取不到值则使用默认值 $nickname = $this->getUser()->getAttribute('nickname', 'Anonymous Coward'); } } CAUTION 你可以在用户会话中保存对象,但是请不要这么作。这是因为会话对象 在不同的请求之间是通过序列化的方式保存在文件里的。从序列化的数据里重建 会话的时候,对象的类必须是已经载入的,不过有的时候他们没有被载入。另外, 如果在会话里保存 Propel 对象,可能会有“卡住”的对象。 就像 symfony 里其他的获取方法一样,getAttribute()方法接受另一个参数,这 个 参 数 指 定一个默认值(如果要存储的用户属性是空的)。hasAttribute()方法 可以用来检查用户属性是否已经被定义了。getAttributeHolder()方法可以用来 访问参数存储器。这也使清除用户属性变得更简单。请参看例 6-18。 例 6-18 - 删除用户会话数据 class mymoduleActions extends sfActions { public function executeRemoveNickname() { $this->getUser()->getAttributeHolder()->remove('nickname'); } public function executeCleanup() { $this->getUser()->getAttributeHolder()->clear(); } } 在模板里,你可以通过储存在$sf_user 变量里的 sfUser 对象访问用户会话属性, 请参看例 6-19。 例 6-19 - 在模板里也可以直接访问用户会话属性Hello, getAttribute('nickname') ?>
NOTE 如果只需要在当前请求里存储信息(例如,在一连串动作调用里)更适合 用 sfRequest 类,它有 getAttribute()和 setAttribute 方法。只有 sfUser 对 象的属性能在不同的请求中持续存在。 短暂的属性 删除用户会话属性(如果不再需要这个属性了)是一个常见的问题。例如,在用 户提交表单后,你想显示一个确认信息。 当处理表单的动作需要转发到另一个 动作时,把 信息从一个动作传到另一个动作唯一的办法就是把信息存在用户会话 的属性里。在显示确认信息之后,你需要删除这个属性。否则,这个属性就会被 存入会话里,一直到会话到期。 你只需要定义短暂的属性,而不需要去删除。因为短暂的属性在接受到下一个请 求后会自动删除,使用户会话更清洁。在动作里,你可以这样来定义短暂的属性: $this->setFlash('attrib', $value); 用户看到这一页后,发出一个新的请求并触发了下一个动作。在这第二个动作里, 你可以这样取得属性的值: $value = $this->getFlash('attrib'); 在显示出下一页后,短暂属性 attrib 就被删除了。即使在下一页里你没有调用 这个属性,它也一样会被从会话里删除。 如果你在模板里调用这个属性,用$sf_flash 对象: has('attrib')): ?> get('attrib') ?> 或: get('attrib') ?> 短暂的属性是传递信息给下一个请求很好的方法。 会话管理 对开发者来说,symfony 的会话功能完全掩饰了对会话 ID 的存储方式。但是, 你仍然可以改变会话管理的默认机制。这是为高级用户设计的。 symfony 把会话 ID 存在客户端的 cookies 上。symfony 的会话 cookies 就叫 symfony, 但是你可以在 factories.yml 里改变会话的名称。 请参看例 6-20。 例 6-20 - 在 apps/myapp/config/factories.yml 里,改变会话的 Cookie 名称 all: storage: class: sfSessionStorage param: session_name: my_cookie_name TIP 会话只有在 factories.yml 里的 auto_start 参数设置成 true 时(这是默认 设置)才会开始开启(通过 PHP 的 session_start()函数)。如果想手动开始用 户会话,关闭会话存储机制里的这个设置就可以了。 symfony 的会话是基于 PHP 会话功能的。这就意味着如果你想用 URL 参数来代替 cookies 的话,你只需要在 php.ini 里修改 use_trans_sid 的设置。 我们不主 张使用这种方法。 session.use_trans_sid = 1 在服务器方面,symfony 把用户会话存在文件系统里面。如果你想把它们存在数 据库里, 你需要修改 factories.yml 里的 class 参数,请参看例 6-21。 例 6-21 - 修改服务器会话的存储方式,在 apps/myapp/config/factories.yml 里 all: storage: class: sfMySQLSessionStorage param: db_table: SESSION_TABLE_NAME # 存放会话的表的名字 database: DATABASE_CONNECTION # 使用的数据库连接的名字 现有的会话存储类有 sfMySQLSessionStorage, sfPostgreSQLSessionStorage, 和 sfPDOSessionStorage, 建议用最后这个。 database 不是必需的配置,它 确定数据库的连接方式;symfony 会用 databases.yml ( 见第 8 章) 里的配置(主 机, 数据库名, 用户名, 密码)去连接数据库。 在 sf_timeout 秒后,会话将自动期满。这个常量的默认值是 30 分钟。当然你可 以在 settings.yml 里修改这个常量。请参看例 6-22。 例 6-22 - 修改会话届期, 在 apps/myapp/config/settings.yml 里 default: .settings: timeout: 1800 # 会话存活的秒数 动作安全 可能会只有某些拥有特定权限的用户能够执行某个动作。symfony 提供的工具可 以让我们建立有安全设置的应用程序,用 户 必 须认证后才能访问应用程序的某些 功能或者部分。权利限制包括两个步骤:首先要声明每一个动作的安全条件,然 后给登录的用户对应的权限。 访问限制 Access Restriction 在执行每一个动作之前,动作都需要经过一个特殊的过滤器。这个过滤器将检查 当前的用户是否有权利执行该动作。 在 symfony 中,权限包括两个部分: · 用户必须被认证后才能执行有安全限制的动作。 · 证书是具名权限,可以通过它来按组管理组织权限。 动作的权限可以在模块的 config/目录的 YAML 配置文件 security.yml 里添加或 修改。在这个文件里,你可以设定每一个动作或所有动作的限制条件。 例 6-23 是 security.yml 的一个示例。 例 6-23 - 设置访问限制, 在 apps/myapp/modules/mymodule/config/security.yml 里 read: is_secure: off # 所有的用户都可以执行 read 动作 update: is_secure: on # update 动作只有认证的用户可以执行 delete: is_secure: on # 只有认证用户 credentials: admin # 并且有 admin 证书可以执行 all: is_secure: off # off 是默认值 动作的权利限制不是默认的。所以如果没有 security.yml 文件,或 security.yml 里面没有对动作的权利限制,任何人都可以执行所有的动作。如果已设定了 security.yml,symfony 将检查该请求是否符合被访问的动作权利限制。 当一 个用户访问一个设有权利限制的动作时, 结果由用户的证书决定: · 如果用户已被认证并有相应的证书,动作将被执行。 · 如果用户没有被认证,他将被跳转到登录的动作上。 · 如果用户已被认证但没有相应的证书,他将被跳转到一个默认的安全动 作。 请参看图 6-1。 默认的登录和安全页面很简单,你可能要重新设计。如果用户没有相应的权利, 你可以在 settings.yml 里指定被调用的动作。请参看例 6-24。 图 6-1 - 默认的安全动作 例 6-24 - 默认的安全动作在 apps/myapp/config/settings.yml 里设置 all: .actions: login_module: default login_action: login secure_module: default secure_action: secure 访问授权 如果要调用设有权限的动作,用户必须被认证后并有相应的证书。你可以用 sfUser 对象扩展用户的权限。 setAuthenticated()方法可以改变用户的认证状 态。 例 6-25 是一个用户认证的简单例子。 例 6-25 - 设置用户的认证状态 class myAccountActions extends sfActions { public function executeLogin() { if ($this->getRequestParameter('login') == 'foobar') { $this->getUser()->setAuthenticated(true); } } public function executeLogout() { $this->getUser()->setAuthenticated(false); } } 认证稍微有一点复杂,你可以检查,添加,删除认证。例 6-26 描述了 sfUser 类里的认证方法。 例 6-26 - 在动作里处理用户的认证 class myAccountActions extends sfActions { public function executeDoThingsWithCredentials() { $user = $this->getUser(); // 增加一个或者两个证书 $user->addCredential('foo'); $user->addCredentials('foo', 'bar'); // 检查用户是否有某个证书 echo $user->hasCredential('foo'); => true // 检查用户是否拥有这些证书中的一个 echo $user->hasCredential(array('foo', 'bar')); => true // 检查用户是否同时拥有两个证书 echo $user->hasCredential(array('foo', 'bar'), true); => true // 删除一个证书 $user->removeCredential('foo'); echo $user->hasCredential('foo'); => false // 删除所有的证书(在登出是特别有用) $user->clearCredentials(); echo $user->hasCredential('bar'); => false } } 如果一个用户有'foo'的证书,这个用户可以访问在 security.yml 里设有该证书 的动作。认证也可以在模板里用来显示被授权的内容。请参看例 6-27。 例 6-27 - 在模板里处理用户的证书- hasCredential('section3')): ?>