[欢迎转载,转载请注名出处
http://watir.cn。本文英文版权归
symfony官方网站所有]
第二天,建立数据模型。
The project unveiled
项目序幕
(译者:在数据库创建前,有必要详细了解一下我们的项目,深入理解了项目,才能设计出完美的数据库。不要像有的人,项目是个什么样子的从来不知道,没有真正用过产品,还在那里指手划脚,能把项目弄好了才怪了呢!所以译者在这里详细翻译了一下。)
What do you want to know? That’s an interesting question. There are many interesting questions, like:
你想知道什么?这是非常有趣的问题。这有许多有趣的问题,就像下面这些:
What shall I do tonight with my girlfriend?
我今天晚上和女朋友共度良宵都做些什么呢?
How can I generate traffic to my blog?
我怎么提高我的blog访问量呢?
What’s the best web application framework?
最好的web开发
框架是什么?
What’s the best affordable restaurant in Paris?
巴黎最好的大众餐馆在哪里呢?
What’s the answer to life, the universe, and everything?
生命是什么,宇宙,还是别的什么?
All these questions don’t have only one answer, and the bestanswer is a matter of opinion. As a matter of fact, the questions thatonly have one answer are often the least interesting (like, how much is1 + 1?) but the only ones to be solved on the web. That’s not fair.
答案并不唯一,最好的答案是能表示一类想法。事实上,往往只有一个答案的问题通常是最乏味的(像1+1=?),在web上解决这一类问题。那就没劲了。
Meet askeet. A website dedicated to help people find answers totheir questions. Who will answer those ticklish questions? Everybody.And everybody will be able to rate other people’s answers, so that themost popular answers get more visibility. As the number of questionsincreases, it becomes impossible to organize them in categories andsub-categories, so the creator of a question will be able to tag itwith any word he/she wants, “à la” del.icio.us. Of course, thepopularity of tags will have to be represented through a tag bubble. Ifone wants to follow the answers to a particular question, he/she cansubscribe to this question’s RSS feed. All these functionalities haveto be elegant and lightweight, so all the interactions that don’tactually need a new page have to be of AJAX type. Eventually, aback-end is necessary to moderate questions and answers reported asspam, or to push artificially a question that the administrator findsencouraging.
现在我们有了askeet,一个可以帮助人们找到所需答案的专业网站。那么谁来回答那些刁钻古怪的问题呢?全世界任何一个人。而且每一个人都可以评价别人给出的答案,流行的答案会有很高的访问量。随着问题数量增长,按照分类或者是子分类来区分基本不可能了,所以问题创建者会用自己的语言给问题赋予标签。当然,流行的标签将会从askeet标签海洋里脱颖而出。如果有人喜欢得到一个问题的详尽答案,他/她会订阅此问题的RSS。所有这些功能实现都需要流畅而简单,互动操作基本需要用AJAX来实现。最后,后台管理程序是必须的,用来禁止显示某些问题和答案,或是把一个管理员认为不错的问题置顶。
Then you should ask: Haven’t I already seen such a website onthe web? Well, if you actually did, we’re busted, but if you refer tofaqts, eHow, Ask Jeeves or something similar, with no collaborativeanswers, no AJAX, no RSS and no tags, this is not the same website. Weare talking about a web 2.0 application here.
那么你要问:这样的网站我不是已经在互联网上看过了吗,如果确实是这样,我们可就破产了,但是如果你喜欢faqts、eHow,询问Jeeves或者其他相似的事,没有collaborative答案,没有AJAX,没有RSS和标签,那么这就不一样了。我们在这里讨论的是web2.0程序。
The big deal about askeet is that it is not only a website, itis an application that anyone can download, install at home or in acompany Intranet, tweak and add features to. The source code will bereleased with an open-source license. Your HR head is looking for aknowledge management system? You want to keep track of all the tricksyou learned about fixing your car? You don’t want to develop aFrequently Asked Questions section for your website? Search no more,for askeet exists. Well, it will exist, that’s our Christmas present.
askeet最大的优点是他不仅仅是个网站,而且
源代码可以免费
下载,在家庭局域网安装或者安装在网上空间,修改和增加新功能。源代码开源。你的HR经理在找一个知识管理
系统吗?你想跟踪保存你学习到的关于修车知识吗?你不会想开发一个总出问题的网站吧?不要找了,askeet在这里。好了,他就在这里,就在这个圣诞诞生。
Data Model
数据模型
Relational model
关系模型
Obviously, there will be a ‘question’ and an ‘answer’ tables.We’ll need a ‘user’ table, and we’ll store the interest of users for aquestion in a ‘interest’ table, and the relevancy given by a person toan answer in a ‘relevancy’ table.
很明显,我们需要有“问题(question)”和“答案(answer)”两个表格。一个“用户(user)”表,一个存储用户提交“有兴趣(interest)”的“兴趣(interset)”表,问题与用户关系的“关联(relevancy)”表。
Users will have to be identified to add a question, to rate therelevancy an answer, or to declare interest to a question. Users won’tneed to be identified to add an answer, but an answer will always belinked to a user so that users with popular answers can bedistinguished. The answers entered without any identification will beshown as contributions of a generic user, called ‘Anonymous Coward’.It’s easier to understand with an entity relationship diagram:
用户参与提出一个问题,评价问题对应的答案,宣布他对那些问题有兴趣。用户不是必须提交答案,但是用户提交了答案,而且这个答案被当作流行答案了,那么这个用户会出名。用户可以匿名提交答案,叫做:Anonymous Coward。应该很易懂。
Notice that we’ve declared a created_at field for each table.Symfony recognizes such fields and sets the value to the current systemtime when a record is created. That’s the same for updated_at fields:Their value is set to the system time whenever the record is updated.
注意我们给每一个表都建了一个created_at字段。symfony能自动识别这一类
字段,当一条记录被加入时,symfony会写入一个当前时间。updated_at字段一样:当信息被更新,系统时间就被重新写入。
schema.xml
The relational model has to be translated to a configurationfile for symfony to understand it. That’s the purpose of the schema.xmlfile, located in the askeet/config/ directory.
关系模型需要被翻译成symfony能识别的配置
文件。这就是schema.xml存在的目的,见askeet/config目录。
There are two ways to write this file: by hand, and that’s theway we like it, or from an existing database. Let’s see the firstsolution. First, we need to rename the sample installed by default:
写这个配置文件有两种方式:第一:手动,这也是我们喜欢的方式;第二:通过一个已存在的数据库来生成配置文件。让我们来看一下第一种方式,我们需要重命名默认的安装例子:
$ svn rename config/schema.xml.sample config/schema.xml
(译者:这里askeet24的教材是让我们通过svn来更新已经存在的配置文件,我们不必这么做,把askeet/config下的schema.yml扩展名改成xml即可。)
The syntax of the schema.xml, explained in detail on the Propelwebsite, is relatively simple: It’s an XML file, in which <table>tags contain <column>, <foreign-key> and <index>tags. Once you write one, you can write all of them. Here is theschema.xml corresponding to the relational model described previously:
schema.xml语法在propel网站上有详细说明,相对来说很简单:就是一个XML文件,用了<table>、<column>、<foreign-key>和<index>标签。如果你亲自写一个,肯定就会写所有的。这是先前的那个关系模型的schema.xml表示:
复制内容到剪贴板
代码:
<?xml version="1.0" encoding="UTF-8"?>
<database name="propel" defaultIdMethod="native" noxsd="true">
<table name="ask_question" phpName="Question">
<column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
<column name="user_id" type="integer" />
<foreign-key foreignTable="ask_user">
<reference local="user_id" foreign="id"/>
</foreign-key>
<column name="title" type="longvarchar" />
<column name="body" type="longvarchar" />
<column name="created_at" type="timestamp" />
<column name="updated_at" type="timestamp" />
</table>
<table name=”ask_answer” phpName=”Answer”>
<column name=”id” type=”integer” required=”true” primaryKey=”true” autoIncrement=”true” />
<column name=”question_id” type=”integer” />
<foreign-key foreignTable=”ask_question”>
<reference local=”question_id” foreign=”id”/>
</foreign-key>
<column name=”user_id” type=”integer” />
<foreign-key foreignTable=”ask_user”>
<reference local=”user_id” foreign=”id”/>
</foreign-key>
<column name=”body” type=”longvarchar” />
<column name=”created_at” type=”timestamp” />
</table>
<table name=”ask_user” phpName=”User”>
<column name=”id” type=”integer” required=”true” primaryKey=”true” autoIncrement=”true” />
<column name=”nickname” type=”varchar” size=”50″ />
<column name=”first_name” type=”varchar” size=”100″ />
<column name=”last_name” type=”varchar” size=”100″ />
<column name=”created_at” type=”timestamp” />
</table>
<table name=”ask_interest” phpName=”Interest”>
<column name=”question_id” type=”integer” primaryKey=”true” />
<foreign-key foreignTable=”ask_question”>
<reference local=”question_id” foreign=”id”/>
</foreign-key>
<column name=”user_id” type=”integer” primaryKey=”true” />
<foreign-key foreignTable=”ask_user”>
<reference local=”user_id” foreign=”id”/>
</foreign-key>
<column name=”created_at” type=”timestamp” />
</table>
<table name=”ask_relevancy” phpName=”Relevancy”>
<column name=”answer_id” type=”integer” primaryKey=”true” />
<foreign-key foreignTable=”ask_answer”>
<reference local=”answer_id” foreign=”id”/>
</foreign-key>
<column name=”user_id” type=”integer” primaryKey=”true” />
<foreign-key foreignTable=”ask_user”>
<reference local=”user_id” foreign=”id”/>
</foreign-key>
<column name=”score” type=”integer” />
<column name=”created_at” type=”timestamp” />
</table>
</database>Notice that the database name is set to propel in this file,whatever the actual database name. This is a parameter used to connectthe Propel layer to the symfony framework. The actual name of thedatabase will be defined in the databases.yml configuration file (seebelow).
注意此文件中数据库名字被写成了propel
(<database name=”propel” defaultIdMethod=”native” noxsd=”true”>),在symfony中无论真正的数据库名字是什么,这个数据库名只是个
变量,用来连接propel layer层到symfony框架的。真正的数据库名字将被在databases.yml配置文件中
定义(见下面)。
There is another way to create a schema.xml if you have anexisting database. That is, if you are familiar with a graphicaldatabase design tool, you will prefer to build the schema from thegenerated MySQL database. Before you do that, you just need to edit thepropel.ini file located in the askeet/config/ directory and enter theconnection settings to your database:
现在来介绍第二种方式:如果数据库已经存在,你可以通过当前数据库来生成schema.xml文件。就是说,如果你熟悉图解式数据库设计工具,你会更喜欢用mysql数据库生成方式建立schema文件。在做这以前,你需要修改propel.ini文件(askeet/config/),输入数据库连接设置如下:
复制内容到剪贴板
代码:
propel.database.url = mysql://username:password@localhost/databasename…where username, password, localhost and databasename are theactual connection settings of your database. You can now call thepropel-build-schema command (from the askeet/ directory) to generatethe schema.xml from the database:
数据库用户名,密码,主机名和数据库名。下面可以用命令行来实现从数据库中生成schema.xml了(在askeet目录里执行):
复制内容到剪贴板
代码:
$ symfony propel-build-schema: some tools allow you to build a database graphically (forinstance Fabforce’s Dbdesigner) and generate directly a schema.xml(with DB Designer 4 TO Propel Schema Converter).
一些工具
软件可以帮你图示生成数据库(例如:Favforce’s Dbdesigner),直接生成schema.xml(用DB Designer 4 TO propel schema Converter)
Instead of creating a schema.xml file, you can also create a schema.yml file using the YAML schema format:
不必创建schema.xml文件,使用schema.yml:
复制内容到剪贴板
代码:
propel:
_attributes: { noXsd: false, defaultIdMethod: none, package: lib.model }
ask_question:
_attributes: { phpName: Question, idMethod: native }
id: { type: integer, required: true, primaryKey: true, autoIncrement: true }
user_id: { type: integer, foreignTable: ask_user, foreignReference: id }
title: { type: longvarchar }
body: { type: longvarchar }
created_at: ~
updated_at: ~
ask_answer:
_attributes: { phpName: Answer, idMethod: native }
id: { type: integer, required: true, primaryKey: true, autoIncrement: true }
question_id: { type: integer, foreignTable: ask_question, foreignReference: id }
user_id: { type: integer, foreignTable: ask_user, foreignReference: id }
body: { type: longvarchar }
created_at: ~
ask_user:
_attributes: { phpName: User, idMethod: native }
id: { type: integer, required: true, primaryKey: true, autoIncrement: true }
nickname: { type: varchar(50), required: true, index: true }
first_name: varchar(100)
last_name: varchar(100)
created_at: ~
ask_interest:
_attributes: { phpName: Interest, idMethod: native }
question_id: { type: integer, foreignTable: ask_question, foreignReference: id, primaryKey: true }
user_id: { type: integer, foreignTable: ask_user, foreignReference: id, primaryKey: true }
created_at: ~
ask_relevancy:
_attributes: { phpName: Relevancy, idMethod: native }
answer_id: { type: integer, foreignTable: ask_answer, foreignReference: id, primaryKey: true }
user_id: { type: integer, foreignTable: ask_user, foreignReference: id, primaryKey: true }
score: { type: integer }
created_at: ~Object model build
建立对象模型
To use the InnoDB engine, one line has to be added to the propel.ini file of the askeet/config/ directory:
使用MySQL.InnoDB引擎,这一行必须加到propel.ini(askeet/config):
复制内容到剪贴板
代码:
propel.mysql.tableType = InnoDBOnce the schema.xml is built, you can generate an object modelbased on the relational model. In symfony, the object relationalmapping is handled by Propel, but encapsulated into the symfony command:
一旦schema.xml被创建,依靠关系模型可以生成对象模型。symfony中,propel映射出关系对象图,已经封装到了命令中,可以直接使用:
复制内容到剪贴板
代码:
$ symfony propel-build-modelThis command (you need to call it from the root directory of theaskeet project) will generate the classes corresponding to the tablesdefined in the schema, together with standard accessors (->get() and->set() methods). You can look at the generated code in theaskeet/lib/model/om/ directory. If you wonder why there are two classesper table, go and check the model chapter of the symfony book. Theseclasses will be overridden each time that you do a build-model, andthis will happen a lot in this project. So if you need to add methodsto the model objects, you have to modify the ones located in theaskeet/lib/model/ directory - these classes inherit from the /om ones.
命令(在askeet根目录里执行)根据schema的定义内容生成对应数据表的类代码,当然也有标准函数方法(->get()和->set())。这些你都可以在askeet/lib/model/om/下查看。如果你疑问为什么每个表有两个class文件,去看一下symfony官方手册model篇。每次当你做build-model(就是运行这个symfonypropel-build-model命令)时那些class都会重建,这在项目中会需要你做很多次的。所以如果你需要给model增加自定义方法,需要修改askeet/lib/model/下的文件——这些class从/om下继承。
(译者:实际上就是说,build-model命令执行时,会自动替换掉askeet/lib/model/om下的那些class文件,你需要写的自定义class都写道上一级目录里,就是askeet/lib/model下。)
(译者:build-model执行了,到底它做了什么呢?实际上就是对数据库的操作封装,那么如果我在lib下放一个mysql.db.class类,封装对数据库的查询、添加、删除、修改操作,实际上和这个没什么两样。个人感觉symfonypropel能自动识别数据库中的某些字段,例如上面说的create_at和update_at,属于自动帮你完成部分工作,这在后面生成app程序时尤其明显。)
The database
数据库
Connection
连接
Now that symfony has an object model of the database, it is timeto connect your project to the MySQL database. First, you have tocreate a database in MySQL:
现在我们有了针对数据库的对象模型,该连接你的数据库了。首先你需要创建一个mysql数据库:
复制内容到剪贴板
代码:
$ mysqladmin -u youruser -p create askeetNow open the askeet/config/databases.yml configuration file. Ifthis is your first time with symfony, you will discover that thesymfony configuration files are written in YAML. The syntax is verysimple, but there is one major obligation in YAML files: never usetabulations, always use spaces. Once you know that, you are ready toedit the file and enter the actual connection settings to your databaseunder the all: category:
打开askeet/config/databases.yml文件。如果你第一次用symfony,你会看到symfony定义文件是用YAML书写的。语法很简单,YAML文件只有一个大问题需要注意:不要使用TAB,用空格。记住这个,开始你的YAML。在文件里写上数据库连接设置,写在all:后面:
复制内容到剪贴板
代码:
all:
propel:
class: sfPropelDatabase
param:
phptype: mysql
host: localhost
database: askeet
username: youruser
password: yourpasswdIf you want to know more about symfony configuration and YAMLfiles, read the configuration in practice chapter of the symfony book.
想知道更多的关于symfony配置和YAML吗?去看
手册。
Build
创建
If you didn’t write the schema.xml file by hand, you probablyalready have the corresponding tables in your database. You can thenskip this part.
如果你是用了上面提到的第二种方式,没有手动书写schema.xml,同样的表应该已经存在于数据库中。你可以跳过此部分。
For you keyboard fans, here is a surprise: You don’t need tocreate the tables and the columns in the MySQL database. You did itonce in the schema.xml, so symfony will build the SQL statementcreating all that for you:
如果你是个键盘狂人
(译者:我就有个这样的朋友,见着人家做网页就喜欢问人家,你这个是不是一行一行div写出来的,他有个怪癖,不喜欢集成开发环境,不过他喜欢textmate… …),那么这对你是个惊喜:你不需要在mysql中建数据库建表。可以在schema.xml中一次性完成,symfony会为你建立所有的sql语句。
复制内容到剪贴板
代码:
$ symfony propel-build-sqlThis command creates a schema.sql in the askeet/data/sql/ directory. Use it as a SQL command in MySQL:
这个命令在askeet/data/sql/下建立schema.sql文件。在mysql中使用即可:
复制内容到剪贴板
代码:
$ mysql -u youruser -p askeet < data/sql/schema.sqlTest data access via a CRUD
用CRUD测试数据库连接
It is always good to see that the work done is useful. Untilnow, your browser wasn’t of any use, and yet we are supposed to build aweb application… So let’s create a basic set of symfony templates andactions to manipulate the data of the ‘question’ table. This will allowyou to create a few questions and display them.
看到一切顺利是好事。直到现在,你的
浏览器还没有用处。我们需要创建web程序……那么让我们创建一个基本的symfony模板templates和动作actions来操作问题(question)表数据。你就可以创建一个新问题并在浏览器中显示它们了。
In the askeet/ directory, type:
进入askeet/根目录,输入:
复制内容到剪贴板
代码:
$ symfony propel-generate-crud frontend question QuestionThis generates a scaffolding for a question module in the frontendapplication, based on the Question Propel object model, with basicCreate Retrieve Update Delete actions (which explains the CRUDacronym). Don’t get confused: A scaffolding is not a finishedapplication, but the basic structure on top of which you can developnew features, add business rules and customize the look and feel.
对前台(frontend)程序question模块建立一个基本程序
脚手架(译者:为了和《The Definitive Guide to symfony 中文版》官方中文翻译同步,我也管scaffolding叫脚手架了),基于Question数据表的propel对象模型,实现了创建(create)、查询( retrieve)、编辑( update)和删除(delete)动作(actions)。别犯晕:这可不是最终的程序,这是基础框架,在这上面你可以开发新部分,增加功能和自定义外观。
The list of all the actions created by a CRUD generator is:
CRUD生成的所有动作(actions)列表:
=============================================
Action name Description
动作名描述:
list shows all the records of a table
list显示数据表中所有记录
index forwards to list
index转到list显示纪录
show shows all the fields of a given record
显示给出记录的详细信息
edit displays a form to create a new record or edit an existing one
edit显示form来创建新纪录或者编辑当前存在的纪录信息
update modifies a record according to the parameters given in the request, then forwards to show
update根据发送过来的信息修改纪录到数据库并且转到show
delete deletes a given record from the table
delete从数据库中删除纪录
==============================================
You can find more about generated actions in the scaffolding chapter of the symfony book.
在symfony手册中关于通过脚手架生成动作有更多内容。
In the askeet/apps/frontend/modules/ directory, notice the new question module and browse its source.
(译者:看到这里,如果你能全看懂,那说明后面你做起来会很轻松;如果你看得有点犯晕,那么提醒你回去补课吧,不然会做起来很辛苦。这里涉及的知识不仅仅是phpoop,phpmvc和symfony手册了,还要加上propel的知识,不仅仅是前面提到的propel-build-model生成基本数据操作类(在askeet/lib/model/om里),这次CRUD生成了在动作action里进行操作的函数,在这里,没有mysql_query($sql),也没有sql语句了,更没有mysql_fetch_array($result)之类的操作了,全部是以一种全新的开发方式呈现在你的眼前。)
在askeet/apps/frontend/modules/下,注意新省城的question模块,仔细看一下源码。
Whenever you add a new class that need to be autoloaded, don’tforget to clear the config cache (to reload the autoloading cache):
如果你增加一个类,需要自动载入,别忘了清理缓存:
复制内容到剪贴板
代码:
$ symfony cc frontend configYou can now test it online by requesting:
现在可以在线测试了,使用下面的url:
http://askeet/question
(译者:这里需要注意一下,我在windowsXP下输入http://askeet/question显示404错误,输入http://askeet/frontend_dev.php/question就正常了,根据apache日志显示,说是askeet/web/question找不到,嘿嘿,邪门了,当然/web下没这个,但是为什么会这样呢,可能又是哪个配置文件在搞怪,时间不多了,这个问题先搁置一下,请高手留言赐教。谢谢!)
[译者2007年10月12日上午11:55分:昨天把24篇文章都翻译完了,今天开始实际写一遍程序加校译。结果又看到这了这个问题,最后还得请教蚂蚱,他来了以后告诉我,是windows系统下URL重写问题,呵呵,其实很简单,把apache配置文件中的#LoadModule rewrite_modulemodules/mod_rewrite.so前面的那个#去掉就好了,我照此法试试,一切OK了!哈哈!在此对蚂蚱表示感谢!]

Go ahead, play with it. Add a few questions, edit them, list them,delete them. If it works, this means that the object model is correct,that the connection to the database is correct, and that the mappingbetween the relational model of the database and the object model ofsymfony is correct. That’s a good functional test.
来吧,让我们继续。加一个新问题,修改,显示,删除。如果正常,就说明我们的工作还顺利。也算做了个好的功能测试。