======我的第一个symfony项目====== // 原文:http://www.symfony-project.com/tutorial/my_first_project.html // 看完对Symfony的介绍,你想要体验一下它的功能?就让我们做一个一小时可完成的全功能网站。 你可以叫它卖书的程式,或是,你也可以叫他网誌(部落格)。就让我们开始吧! 我们将假设你已经装好了apache/PHP5(如果你想快一点,你可用[[http://www.apachefriends.org/en/projects.html|xampp/lampp]])。 你也需要一个SQLite。目前预设已经和PHP5整合好了。然而,若是PHP 5.1.0,你必须要用php.ini开启PHP对SQLite的支持([[http://us3.php.net/sqlite|详情]])。 =====安装symfony并初始化一个专案===== 為了加快速度,我们用了[[http://www.symfony-project.com/get/sf_sandbox-alpha.tgz|symfony sandbox]]。它是个空的symfony专案,且包含了所有的symfony函式库,而且最基础的设定也完备了。用sandbox的最大好处,就是你可以马上体验symfony 。 先下载[[http://www.symfony-project.com/get/sf_sandbox-alpha.tgz|sandbox]]。然后在你的网站根目录解压缩。你可参考readme已得到更多的资讯。解开后的档案结构应该像这样: www/ sf_sandbox/ apps/ frontend/ batch/ cache/ config/ data/ sql/ doc/ api/ lib/ model/ log/ test/ web/ css/ images/ js/ uploads/ 这表示sf_sandbox已经有了前端程式,你可在你的网址列打上 http://localhost/sf_sandbox/web/index.php/ 你将看到一个恭喜的网页: #sym[{{symfony:first_tub:first_congrats.gif|}}]# #note[**注意**:如果你看不到这个恭禧你安装成功的网页,请你检查php.ini这个配置文件。找这个设定值“magic_quotes_gpc”把它设定成off. 如需要更多的帮忙,请参考[[http://www.symfony-project.com/forum/index.php/f/6/|安装论坛]],那里有超多特殊的状况,被一一解决。]# 你可以安装symfony在你自己喜欢的目录下。然后设定你的网站指向它。或是设成虚拟网站或是别名网站,symfony的书有详细的章节介绍,请参考[[http://www.symfony-project.com/forum/index.php/f/6/|symfony installation]], [[http://www.symfony-project.com/content/book/page/project_creation.html|project creation]] and [[http://www.symfony-project.com/content/book/page/file_structure.html|file structure]]这几章。 =====初始化资料模型===== 当然,一般的网志,可以处理贴文的。然后你也可以在其上加些註解评论。在目录sf_sandbox/config/新增一个结构设定档schema.yml,然后贴上如下的内文。 propel: weblog_post: _attributes: { phpName: Post } id: title: varchar(255) excerpt: longvarchar body: longvarchar created_at: weblog_comment: _attributes: { phpName: Comment } id: post_id: author: varchar(255) email: varchar(255) body: longvarchar created_at: 这个设定档用的是YAML文法,它是一个很简单的语言,用类似XML的树状阶层的架构来定义资料模型(资料表)。更进一步,它的读写比XML还快。这唯一要注意的是,缩排是有意义的,且要避免使用TAB键,所以请记住用空白键(SPACE)来表示少缩排。 你可以在[[http://www.symfony-project.com/content/book/page/configuration.html|configuration chapter]]这章,找到更多关於YAML和symfony的设定须知。 这个结构档描述在网志程式里所需要用到的两个资料表。Post和Comment是未来将会產生相对应的类别。存档,然后进入指令列。进到目录sf_sandbox/,然后键入:
$ symfony propel-build-model
#note[**注意**:在*nix平台,你可能是用./symfony.sh而不是键入symfony]# 一些类别会产生在这个目录sf_sandbox/lib/model/,它们是物件/关係映射(object-relational mapping)的产物。这些类别的好处是我们可以用物件导向的方式而不是下SQL的INSERT/UPDATE指令来存取关联性资料库。Symfony使用Propel函式库来来达到此目的。我们将叫这些物件为模型(model)。(更多关于[[http://www.symfony-project.com/content/book/page/model.html|模型]]的说明) 你现在可以在指令列,输入
$ symfony propel-build-sql
一个schema.sql 档產生在sf_sandbox/data/sql/。里面的SQL句子可以產生一个资料库,里面含这两资料表。你可以下指令或用WEB界面([[https://sourceforge.net/projects/phpmyadmin/|phpMyAdmin]])在MYSQL产生资料库。(在这章[[http://www.symfony-project.com/content/book/page/model.html|model]]也有讲到)。幸运地symfony sandbox 已经设定好直接可用SQLite(轻便的档案型资料库)。所以不用再做一些初始设定。预设的状况下,sf_sandbox专案将使用sf_sandbox/data/目录下的一个sandbox.db。如果你要在这个档案型资料库建资料表。你可以键入
$ symfony propel-insert-sql
#note[**注意**:在这里有警告讯息也不用紧张,这是正常的,insert-sql的动作是先移除资料很再建立资料表,第一次做之前,当然没资料表可移除。]# =====创建程序棚架(scaffolding)===== 众所周知,一个基本的网志可让用户建立,检索,更新,删除贴子和留言(CRUD)。考虑到你还是Symfony的新手,你将不会要求从零开始写Symfony的代码,而是让Symfony帮你创建CRUD的棚架。棚架建好之后,你可以根据需要而修改CRUD的代码。输入:
$ symfony propel-generate-crud frontend post Post
$ symfony propel-generate-crud frontend comment Comment
#note[**注意**: 确定你在Symfony专案的根目录下执行这些指令]# 现在你建立了两个模块(post and comment),可让你支配Post和Comment类的对象。一个模块通常用来代表一个或一组有相似功能的网页。你新建两个模块位于sf_sandbox/apps/frontend/modules/目录里。相应的,它们可以访问这两个模块通过以下地址: http://localhost/sf_sandbox/web/frontend_dev.php/post http://localhost/sf_sandbox/web/frontend_dev.php/comment 点击'''create'''添加几个测试贴: #sym[{{symfony:first_tub:first_crud.gif|}}]# 你可以在[[http://www.symfony-project.com/content/book/page/generator.html|scaffolding]]和[[http://www.symfony-project.com/content/book/page/project_creation.html|structure]]找到更多关于棚架和symfony专案构架的说明。 #note[**注意**:以上的两个地址的运行脚本 - 在Symfony里称为''front controller'' - 由index.php变为frontend_dev.php。两个脚本都执行一样的运用程序,区别是它们使用不同的执行参数。frontend_dev.php使用'''开发环境'''的参数,目的在于提供一系列的调试工具,例如'''debug toolbar'''位于屏幕的右上角。这就是为什么frontend_dev.php在处理页面的时候比index.php要慢。index.php则使用'''生产环境'''的参数。如果你在开发时需要用到用'''生产环境''',只需把frontend_dev.php改为index.php,然后清除缓存:
$ symfony clear-cache
]# 更多关于[[http://www.symfony-project.com/content/book/page/configuration.html|运行环境]]的说明 =====修改模板===== 要在我们新建两个模块之间导航,我们的网页需共享超连结。打开公用模板sf_sandbox/apps/frontend/templates/layout.php,把之间的内容修改为:

kk

kk
请原谅不精心的设计和使用inline CSS,一个小时的时间并不是很充裕。 #sym[{{symfony:first_tub:first_crud_layout.gif|}}]# 你可以修改页面的标题。打开转案的配置文件sf_sandbox/apps/frontend/config/view.yml, 找出有title的一行,修改为: default: http_metas: content-type: text/html; charset=utf-8 metas: title: The best weblog ever robots: index, follow description: symfony project keywords: symfony, project language: en 你目前的主页使用默认的模版(template)和模块(module),位于Symfony框架目录下,而不在你的专案目录下。如果要覆盖(override)它,你必须建立自己的默认模块。输入:
$ cd apps/frontend/modules
$ mkdir default
$ cd default
$ mkdir templates
$ cd templates
新建文件indexSucess.php,加入以下的内容:

Welcome to my swell weblog

You are the th visitor today.

导航到以下地址测试新的页面: http://localhost/sf_sandbox/web/frontend_dev.php/ #sym[{{symfony:first_tub:first_welcome.gif|}}]# 测试:建立一个贴,然后测试回复。 更多关于[[http://www.symfony-project.com/content/book/page/view.html|模版]]和[[http://www.symfony-project.com/content/book/page/templating_configuration.html|视图设置]]的说明 =====把数据从动作传递到模版===== 这一节,我们要做的是在贴的下面显示回复。 首先,你必须编辑**动作**(action)。打开sf_sandbox/apps/frontend/modules/post/actions/actions.class.php,找到函数executeShow(),加入下面的四行代码: public function executeShow () { $this->post = PostPeer::retrieveByPk($this->getRequestParameter('id')); $c = new Criteria(); $c->add(CommentPeer::POST_ID, $this->getRequestParameter('id')); $c->addAscendingOrderByColumn(CommentPeer::CREATED_AT); $this->comments = CommentPeer::doSelect($c); $this->forward404Unless($this->post instanceof Post); } Criteria和Peer是Propel的对象关系映射(object-relational mapping)的一部分。总之,这四行代码将会从SQL中调出与Post相关联的Comment(Post指定在URL的id参数)。$this->comments这行主要是让与动作相对应的模版读取$comments里的数据。下一步我们就可以修改模版显示数据了。打开sf_sandbox/apps/frontend/modules/post/templates/showSuccess.php,在最后一行加入: ...

comment1) : ?>s to this post.

posted by getAuthor() ?> on getCreatedAt()) ?>

getBody()) ?>
本页使用了两个新的由Symfony提供的函数(format_date()和simple_format_text())。在Symfony里这些函数称为**助手(helpers)**,原因是它们能够提供一些很便利的功能。为你的第一个贴添加一个新的回复,然后查看: http://localhost/sf_sandbox/web/frontend_dev.php/post/show?id=1 #sym[{{symfony:first_tub:first_comments_under_post.gif|}}]# 你可以在[[http://www.symfony-project.com/content/book/page/view.html|命名协议]]找到更多关于动作和模版的联系,或查看[[http://www.symfony-project.com/content/book/page/templating_other_helpers.html|text]]和[[http://www.symfony-project.com/content/book/page/templating_i18n_helpers.html|data]]的说明。 =====添加与另一个资料表相关联的档案===== 你可以依据Post的Id添加档案,但这不是用户界面友好设计的做法。让我们修改这一不便利之处。另外,当用户回复后,别忘了把他重定向到原来的贴。 首先,打开modules/post/templates/showSuccess.php模板,在最后一行加入: getId()) ?> link_to()是一个助手,它建立一个超链接指向comment模块的create动作,目的是让你可以从看贴的页直接回复。下一步,打开imodules/comment/templates/editSuccess.php,把这几行: Post*: 'Post', )) ?> 更替为: hasParameter('post_id')): ?> getParameter('post_id')) ?> Post*: 'Post', )) ?> comment/create页的表单指向comment/update的动作,而且当用户提交表单之后,comment/update的动作会把页面重定向到comment/show(这是CRUDs的默认处理方式)。对我们的网志来说,这意味着在添加回复后,只有刚回复的comment显示在页面。为了达到户界面友好的设计,在用户回复后,把贴和回复显示在同一页面。打开modules/comment/actions/actions.class.php,把executeupdate()修改为: public function executeUpdate () { if (!$this->getRequestParameter('id', 0)) { $comment = new Comment(); } else { $comment = CommentPeer::retrieveByPk($this->getRequestParameter('id')); $this->forward404Unless($comment); } $comment->setId($this->getRequestParameter('id')); $comment->setPostId($this->getRequestParameter('post_id')); $comment->setAuthor($this->getRequestParameter('author')); $comment->setEmail($this->getRequestParameter('email')); $comment->setBody($this->getRequestParameter('body')); $comment->save(); return $this->redirect('post/show?id='.$comment->getPostId()); } 现在,用户能在回复后回到原来的界面。到此为止,你已完成了一个基本的网志。 你可以在[[http://www.symfony-project.com/content/book/page/controller.html|这里]]找到更多关于动作的说明 =====表单验证===== 用户可以回贴,但是,如果有谁提交空白的表单,数据库的质量将会受到殃及。避免此类情况发生,在sf_sandbox/apps/frontend/modules/comment/validate/目录下创建一个新的文件update.yml,加入: methods: post: [author, email, body] get: [author, email, body] fillin: activate: on names: author: required: Yes required_msg: The name field cannot be left blank email: required: No validators: emailValidator body: required: Yes required_msg: The text field cannot be left blank emailValidator: class: sfEmailValidator param: email_error: The email address is not valid. #note[**注意**:复制时务必保留原来的格式,文件务虚以**methods**的**m**开头,不能有空格]# 启动fillin可使表单在验证失败的情况下,自动导入用户上一次输入的数据。names定义表单的验证规则。 在默认情况下,当验证失败,控制器(controller)将会把用户转updateError.php模板。较可取的做法是当验证失败时,在同一页面显示表单和失败原因。打开modules/comment/actions/actions.class.php,加入函数: public function handleError() { $this->forward('comment', 'create'); } 打开modules/comment/templates/editSuccess.php模版,在第一行插入: hasErrors()): ?>
Please correct the following errors and resubmit:
你完成了一个具有[[http://blog.sina.com.cn/myblog/article/article_print.php?blog_id=49e9696c010004xl | 鲁棒性(robust)]]的表单。 #sym[{{symfony:first_tub:first_form_validation.gif|}}]# 更多关于[[http://www.symfony-project.com/content/book/page/validate_form.html|表单验证]]的说明 =====修改URL===== 我们可以让我们网志的URL更加亲和用户和搜索引擎。就用贴的标题作为URL吧。 标题为URL存在一个问题 - 标题可含有特殊字符如空格。你可以转换字符,但在有空格的情况下会产生不美观的URL(例如%20)。所以最好是在Post模型(model)中加入一个清理标题的函数。打开sf_sandbox/lib/model/Post.php,添加函数: public function getStrippedTitle() { $result = strtolower($this->getTitle()); // strip all non word chars $result = preg_replace('/\W/', ' ', $result); // replace all white space sections with a dash $result = preg_replace('/\ /', '-', $result); // trim dashes $result = preg_replace('/\-$/', '', $result); $result = preg_replace('/^\-/', '', $result); return $result; } 现在你能为post模块建立一个permalink动作. 打开modules/post/actions/actions.class.php,加入函数: public function executePermalink() { $posts = PostPeer::doSelect(new Criteria()); $title = $this->getRequestParameter('title'); foreach ($posts as $post) { if ($post->getStrippedTitle() == $title) { break; } } $this->forward404Unless($post); $this->getRequest()->setParameter('id', $post->getId()); $this->forward('post', 'show'); } 打开模版modules/post/templates/listSuccess.php, 把显示id的部分去掉,且把 getTitle() ?> 修改为: getTitle(), '/'.$sf_last_module.'/permalink?title='.$post->getStrippedTitle()) ?> 最后,打开sf_sandbox/apps/frontend/config/routing.yml,加入: list_of_posts: url: /latest_posts param: { module: post, action: list } post: url: /weblog/:title param: { module: post, action: permalink } 打开网页测试新的URL。 #sym[{{symfony:first_tub:first_routing.gif|}}]# 更多关于[[http://www.symfony-project.com/content/book/page/routing.html|智慧URL]]的说明 =====整理前端程序===== 此步主要是限制用户的权限 - 只给用户阅读的权力。 打开modules/post/templates/showSuccess.php模版,删除这行: getId()) ?> 同样地,打开modules/post/templates/listSuccess.php模版,删除: 打开modules/post/actions/actions.class.php,删除函数: * executeCreate * executeEdit * executeUpdate * executeDelete 到此为止,用户只拥有阅读的权限了。 =====制作后端程序===== 让我们制作一个可让我们发帖的后端程序。在转案的根目录下(sf_sandbox),输入:
$ symfony init-app backend
$ symfony propel-init-admin backend post Post
$ symfony propel-init-admin backend comment Comment
这一次,我们用[[http://www.symfony-project.com/content/book/page/generator.html|管理生成器(Admin generator)]],它比CRUD更强大且具更多的功能。 打开apps/backend/template/layout.php模版,加入共享导航链接:
#note[**注意**:Note: Because you are using a sandbox, you also need to copy the sf_sandbox/web/sf/images/sf_admin/ directory in a sf/images/sf_admin/ directory in your web root folder (this is due to image paths in CSS stylesheets).(这段不是很明确,有待翻译)]# 你能访问刚做好的后端程序 - 以调试环境(develop environment): http://localhost/sf_sandbox/web/backend_dev.php/post #sym[{{symfony:first_tub:first_basic_admin.gif|}}]# 使用Admin generator最大的好处是通过配置文件,你可以轻易地制作自定义方案(configuration file)。 修改backend/modules/post/config/generator.yml: generator: class: sfPropelAdminGenerator param: model_class: Post theme: default fields: title: { name: Title } excerpt: { name: Exerpt } body: { name: Body } nb_comments: { name: Comments } created_at: { name: Creation date } list: title: Post list layout: tabular display: [=title, excerpt, nb_comments, created_at] object_actions: _edit: - _delete: - max_per_page: 5 filters: [title, created_at] edit: title: Post detail fields: title: { type: input_tag, params: size=53 } excerpt: { type: textarea_tag, params: size=50x2 } body: { type: textarea_tag, params: size=50x10 } created_at: { type: input_date_tag, params: rich=on } 以上的Post model有一列nb_comments。我们的模型(model)没有提供这一函数。打开sf_sandbox/lib/model/Post.php模型,加入下列函数: public function getNbComments() { return count($this->getComments()); } 打开页面,更新(refresh),测试刚完成的模块。 #sym[{{symfony:first_tub:first_custom_admin.gif|}}]# =====限制访问后端程序的权限===== 目前的后端程序没有限制访问,你应该限制它的访问权限。 新建一个apps/backend/modules/post/config/security.yml文件,加入: all: is_secure: on 为comment模块新建同样的文件。 现在,你不能访问以上的两个模块由于还未登录。登录模块尚未建立,所以我们现在就建一个。创建登录模块,输入:
$ symfony init-module backend security
新建的模块将用来处理登陆请求。打开apps/backend/modules/security/templates/indexSuccess.php模版,加入:

Authentication

hasErrors()): ?> Identification failed - please try again get('login')) ?>
打开apps/backend/modules/security/actions/actions.class.php,添加一个登陆的动作: public function executeLogin() { if ($this->getRequestParameter('login') == 'admin' && $this->getRequestParameter('password') == 'password') { $this->getUser()->setAuthenticated(true); return $this->redirect('default/index'); } else { $this->getRequest()->setError('login', 'incorrect entry'); return $this->forward('security', 'index'); } } 最后一步,把security模块设为默认的登陆模块。具体做法是,打开apps/backend/config/settings.yml,加入: all: .actions: login_module: security login_action: index 如果你现在访问Post的后端程序,你将会要求输入帐号和密码。 #sym[{{symfony:first_tub:first_login.gif|}}]# 更多关于[[http://www.symfony-project.com/content/book/page/security.html|安全]]的说明 =====结论===== 恭喜,你完成了一个网路日志。你可以在做业环境测试它的性能: frontend: http://localhost/sf_sandbox/web/index.php/ backend: http://localhost/sf_sandbox/web/backend.php/ 如果在做业环境下发生错误,大多数情况出于缓存的问题。清除缓存,输入:
$ symfony cc
全文完 ======讨论/问题====== 有关于教程的问题请加在[[discuss:我的第一个symfony项目|这里]]。