原文:http://www.symfony-project.com/tutorial/my_first_project.html
看完对Symfony的介绍,你想要体验一下它的功能?就让我们做一个一小时可完成的全功能网站。 你可以叫它卖书的程式,或是,你也可以叫他网誌(部落格)。就让我们开始吧!
我们将假设你已经装好了apache/PHP5(如果你想快一点,你可用xampp/lampp)。
你也需要一个SQLite。目前预设已经和PHP5整合好了。然而,若是PHP 5.1.0,你必须要用php.ini开启PHP对SQLite的支持(详情)。
為了加快速度,我们用了symfony sandbox。它是个空的symfony专案,且包含了所有的symfony函式库,而且最基础的设定也完备了。用sandbox的最大好处,就是你可以马上体验symfony 。
先下载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/
你将看到一个恭喜的网页:
注意:如果你看不到这个恭禧你安装成功的网页,请你检查php.ini这个配置文件。找这个设定值“magic_quotes_gpc”把它设定成off. 如需要更多的帮忙,请参考安装论坛,那里有超多特殊的状况,被一一解决。
你可以安装symfony在你自己喜欢的目录下。然后设定你的网站指向它。或是设成虚拟网站或是别名网站,symfony的书有详细的章节介绍,请参考symfony installation, project creation and 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)来表示少缩排。
你可以在configuration chapter这章,找到更多关於YAML和symfony的设定须知。
这个结构档描述在网志程式里所需要用到的两个资料表。Post和Comment是未来将会產生相对应的类别。存档,然后进入指令列。进到目录sf_sandbox/,然后键入:
$ symfony propel-build-model
注意:在*nix平台,你可能是用./symfony.sh而不是键入symfony
一些类别会产生在这个目录sf_sandbox/lib/model/,它们是物件/关係映射(object-relational mapping)的产物。这些类别的好处是我们可以用物件导向的方式而不是下SQL的INSERT/UPDATE指令来存取关联性资料库。Symfony使用Propel函式库来来达到此目的。我们将叫这些物件为模型(model)。(更多关于模型的说明)
你现在可以在指令列,输入
$ symfony propel-build-sql
一个schema.sql 档產生在sf_sandbox/data/sql/。里面的SQL句子可以產生一个资料库,里面含这两资料表。你可以下指令或用WEB界面(phpMyAdmin)在MYSQL产生资料库。(在这章model也有讲到)。幸运地symfony sandbox 已经设定好直接可用SQLite(轻便的档案型资料库)。所以不用再做一些初始设定。预设的状况下,sf_sandbox专案将使用sf_sandbox/data/目录下的一个sandbox.db。如果你要在这个档案型资料库建资料表。你可以键入
$ symfony propel-insert-sql
注意:在这里有警告讯息也不用紧张,这是正常的,insert-sql的动作是先移除资料很再建立资料表,第一次做之前,当然没资料表可移除。
众所周知,一个基本的网志可让用户建立,检索,更新,删除贴子和留言(CRUD)。考虑到你还是Symfony的新手,你将不会要求从零开始写Symfony的代码,而是让Symfony帮你创建CRUD的棚架。棚架建好之后,你可以根据需要而修改CRUD的代码。输入:
$ symfony propel-generate-crud frontend post Post $ symfony propel-generate-crud frontend comment Comment
注意: 确定你在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‘添加几个测试贴:
你可以在scaffolding和structure找到更多关于棚架和symfony专案构架的说明。
注意:以上的两个地址的运行脚本 - 在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
更多关于运行环境的说明
要在我们新建两个模块之间导航,我们的网页需共享超连结。打开公用模板sf_sandbox/apps/frontend/templates/layout.php,把<body>之间的内容修改为:
<div id="container" style="width:600px;margin:0 auto;border:1px solid grey;padding:10px"> <div id="navigation" style="display:inline;float:right"> <ul> <li>k</li> <li></li> </ul> </div> <div id="title"> <h1>kk</h1> </div> <div id="content" style="clear:right"> kk </div> </div>
请原谅不精心的设计和使用inline CSS,一个小时的时间并不是很充裕。
你可以修改页面的标题。打开转案的配置文件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,加入以下的内容:
<h1>Welcome to my swell weblog</h1> <p>You are the <?php echo rand(1000,5000) ?>th visitor today.</p>
导航到以下地址测试新的页面:
http://localhost/sf_sandbox/web/frontend_dev.php/
测试:建立一个贴,然后测试回复。
这一节,我们要做的是在贴的下面显示回复。 首先,你必须编辑动作(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,在最后一行加入:
... <?php use_helper('Text') ?> <?php use_helper('Date') ?> <hr /> <?php if($comments) : ?> <p><?php echo count($comments) ?> comment<?php if(count($comments)>1) : ?>s<?php endif ?> to this post.</p> <?php foreach ($comments as $comment): ?> <p><em>posted by <?php echo $comment->getAuthor() ?> on <?php echo format_date($comment->getCreatedAt()) ?></em></p> <div class="comment" style="margin-bottom:10px;"> <?php echo simple_format_text($comment->getBody()) ?> </div> <?php endforeach ?> <?php endif ?>
本页使用了两个新的由Symfony提供的函数(format_date()和simple_format_text())。在Symfony里这些函数称为助手(helpers),原因是它们能够提供一些很便利的功能。为你的第一个贴添加一个新的回复,然后查看:
http://localhost/sf_sandbox/web/frontend_dev.php/post/show?id=1
你可以依据Post的Id添加档案,但这不是用户界面友好设计的做法。让我们修改这一不便利之处。另外,当用户回复后,别忘了把他重定向到原来的贴。 首先,打开modules/post/templates/showSuccess.php模板,在最后一行加入:
<?php echo link_to('Add a comment','comment/create?post_id='.$post->getId()) ?>
link_to()是一个助手,它建立一个超链接指向comment模块的create动作,目的是让你可以从看贴的页直接回复。下一步,打开imodules/comment/templates/editSuccess.php,把这几行:
<tr> <th>Post*:</th> <td><?php echo object_select_tag($comment, 'getPostId', array ( 'related_class' => 'Post', )) ?></td> </tr>
更替为:
<?php if ($sf_request->hasParameter('post_id')): ?> <?php echo input_hidden_tag('post_id',$sf_request->getParameter('post_id')) ?> <?php else: ?> <tr> <th>Post*:</th> <td><?php echo object_select_tag($comment, 'getPostId', array ( 'related_class' => 'Post', )) ?></td> </tr> <?php endif ?>
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()); }
现在,用户能在回复后回到原来的界面。到此为止,你已完成了一个基本的网志。
你可以在这里找到更多关于动作的说明
用户可以回贴,但是,如果有谁提交空白的表单,数据库的质量将会受到殃及。避免此类情况发生,在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.
注意:复制时务必保留原来的格式,文件务虚以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模版,在第一行插入:
<?php if ($sf_request->hasErrors()): ?> <div id="errors" style="padding:10px;"> Please correct the following errors and resubmit: <ul> <?php foreach($sf_request->getErrors() as $error): ?> <li><?php echo $error ?></li> <?php endforeach ?> </ul> </div> <?php endif ?>
你完成了一个具有 鲁棒性(robust)的表单。
更多关于表单验证的说明
我们可以让我们网志的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的部分去掉,且把
<td><?php echo $post->getTitle() ?></td>
修改为:
<td><?php echo link_to($post->getTitle(), '/'.$sf_last_module.'/permalink?title='.$post->getStrippedTitle()) ?></td>
最后,打开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。
更多关于智慧URL的说明
此步主要是限制用户的权限 - 只给用户阅读的权力。
打开modules/post/templates/showSuccess.php模版,删除这行:
<?php echo link_to('edit', 'post/edit?id='.$post->getId()) ?>
同样地,打开modules/post/templates/listSuccess.php模版,删除:
<?php echo link_to('create', 'post/create') ?>
打开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
这一次,我们用管理生成器(Admin generator),它比CRUD更强大且具更多的功能。
打开apps/backend/template/layout.php模版,加入共享导航链接:
<div id="navigation"> <ul style="list-style:none;"> <li><?php echo link_to('Manage posts', 'post/list') ?></li> <li><?php echo link_to('Manage comments', 'comment/list') ?></li> </ul> </div> <div id="content"> <?php echo $sf_content ?> </div>
注意: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
使用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),测试刚完成的模块。
目前的后端程序没有限制访问,你应该限制它的访问权限。
新建一个apps/backend/modules/post/config/security.yml文件,加入:
all: is_secure: on
为comment模块新建同样的文件。
现在,你不能访问以上的两个模块由于还未登录。登录模块尚未建立,所以我们现在就建一个。创建登录模块,输入:
$ symfony init-module backend security
新建的模块将用来处理登陆请求。打开apps/backend/modules/security/templates/indexSuccess.php模版,加入:
<h2>Authentication</h2> <?php if ($sf_request->hasErrors()): ?> Identification failed - please try again <?php endif ?> <?php echo form_tag('security/login') ?> <label for="login">login:</label> <?php echo input_tag('login', $sf_params->get('login')) ?> <label for="password">password:</label> <?php echo input_password_tag('password') ?> <?php echo submit_tag('submit', 'class=default') ?> </form>
打开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的后端程序,你将会要求输入帐号和密码。
更多关于安全的说明
恭喜,你完成了一个网路日志。你可以在做业环境测试它的性能:
frontend: http://localhost/sf_sandbox/web/index.php/ backend: http://localhost/sf_sandbox/web/backend.php/
如果在做业环境下发生错误,大多数情况出于缓存的问题。清除缓存,输入:
$ symfony cc
全文完
有关于教程的问题请加在这里。