第二天--设置一个数据模型
Symfony回顾 在这份长长的但是却十分有趣的指南的第一天内容中,我们了解了如何安装Symfony框架,设置一个新程序以及开发环境,并且使用了源码版本控制安全的存放源码。顺便说一句,在第一天所生成的程序源码可以在askeet的源码仓库得到: http://svn.askeet.com/ 第二天的目标是从功能的角度来定义最终的结果应是什么样子的,设计数据模型以及编码。这包括生成一个对象关系映射,并且使用他们来创建,取出以及更新程序框架数据中的记录。 这确实是相当多的内容。让我们开始吧。 揭开程序面纱 我们希望了解什么呢?这是一个有趣的问题。对于这个问题有许多有趣的答案,如: 如何带来我的blog的流量? 什么是最好的web程序框架? 所有这些问题不只有一个答案,而最好的答案在于我们的观点。实际上,只有一个答案的问题通常是最无趣的,但是在web上要解决的只有一个。这不公平。 来askeet 吧。这是一个帮助人们查找问题答案的网站。谁会回答这些问题呢?所有人。而每一个人都可以评价其他人的答案,所以最受欢迎的答案会更可见。随着问题数量的 增加,使用类与子类的方式来组织就变得不可行了,所以问题的提出者就可以使用他希望的任何单词来进行标签化。当然,标签的流行程序是由一个标签集合来表示 的。如果一个希望跟随一个答案来找到一个问题,他可以订阅这个问题RSS反馈。所有这些功能都必须是优雅和轻量级的,从而所有这些交互不必需要一个 AJAX类型式的新页。事实上,需要一个后端来组织问题与答案,或是手工添加一个管理员认为有意义的问题。 也许你会问:我还没有在web 上见到过这样的网站吗?当然,如果你确实是这样,那么我们正好,但是如果你见过如faqts,eHow,Ask leeves或是相似的内容,没有聚合答案,没有AJAX,没有RSS,没有标签,那么这些与我们的网站并不一样。我们这里在讨论一个web2.0的程 序。 askeet的目录不只是一个网站,他是一个程序,任何人都可以下载,在家里或是在公司的网站上安全,修改或是添加新的特性。源码将 会以开放源码许可证的形式发布。你的HR经理正是寻找一个知识管理系统?你希望记录你学到的关于修理汽车的技艺?你并不希望为你的网站开发一个FAQ部 分?不必再寻找了,因为有askeet。当然,他将会存在,那是我们的圣诞礼物。 从哪里开始? 那么你如何开始一个Symfony程序呢?这取决于你自己。如果你是一个XP能手,你可以写一个故事,计划一个游戏,并且找到一个合作者来进行结对编程,或者如果你是一个UML迷,你可以编写一个详细的网站需要说明,附带一个所有对象,状态以及交互的框架。 但 是这个教程并不是关于通常的程序开发的,所以我们从一个基本的关系数据模型开始,并且一步一步的添加工作特性。我们所需要的只是一个在每天结束时可用的程 序,而不是一个不会输出任何内容的巨大的程序代码。在理想的情况下,我们应为我们添加的每一个新特性编写单元测试,但是实际上我们并没有时间来这样做。如 果要进行单元测试,则需要一天的时间。所以我们还是继续阅读吧。 对于这个工程,我们将使用一个带有InonoDB表类型的MySQL数据 库,这样可以充发利用集中控制与事务支持。在前面的步骤中,我们将会使用一个SQLite数据库,来避免设计一个实际的数据库。这需要 databases.yml文件中时行一些小的修改,我们会将这些工作作为探索的练习留给你。 数据模型 关系模型 很明显,需要有一个'question'与一个'answer'数据表。我们需要一个'user'数据表,并且我们需要一个'interest'数据表来存储对一个问题感兴趣的用户,以及在一个'relevancy'数据表中记录一个用户对于一个答案所做出的中肯的评价。 用 户需要进行确认来添加一个问题,评价答案,或者是删除对于一个问题的兴趣。用户添加答案并不需要进行确认,但是一个答案总是会链接到一个用户,这样给出最 受欢迎答案的用户可以进行区分。没有经过确认而发布的答案将会显示为一个通常用户的贡献,称之为'Anonymous Coward'。很容易理解整个关系数据表: ERD 注意,对于每一个数据表,我们都声明了一个created_at域。Symfony会识别这样的域,并且在记录创建时会将其设置当前的系统时间。这也updated_at域相似:当记录更新时会将其设置为系统时间。 schema.xml 关系模型已经转换为一个Symfony可以理解的配置文件。这也就是schema.xml或是schema.yml文件的目的,这个文件位于askeet/config/目录下。Symfony支持XML或是YAML的格式语法。 有两种方法来编写这个文件:手写,这也我们喜欢的方法,或者由一个已存在的数据库生成。让我们看一下第一种解决方法。 首先,我们需要移除默认安装的YAML样式文件: $ svn delete config/schema.yml schema.yml 文件的语法是相当简单:他是一个XML文件,在其中<table>标记包含<column>,<foreign- key>以及<index>标记。一旦我们编写了一个,我们就可以编写所有的。下面是我们在前面所描述的关系模型的对应 schema.yml文件: <?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> 注意,在这个文件中将数据名字设置为propel,而无论实际的数据库名字是什么。这个是一个用来连接Propel层与Symfony框架的参数。数据库的实际名字将会在databases.yml配置文件中定义。 如 果我们有一个数据库,我们还有另外的一个方法来创建schema.yml文件。也就是说,如果我们熟悉一个图形化的数据库设计工具,我们会更喜欢由生成的 MySQL数据库来构建schema。在我们做这项工作之前,我们只需要编辑位于askeet/config/目录下的propel.ini文件,输入到 我们数据库的连接字符串: propel.database.url = mysql://username:password@localhost/databasename 这里的username,password,localhost以及databasename是我们数据库的实际连接设置。现在我们可以调用propel-build-schema命令(在askeet/目录下)来由数据库生成schema.xml: $ symfony propel-build-schema 除了创建一个schema.xml文件,我们也可以使用YAML的语法格式来创建一个schema.yml文件: [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 } user_id: { type: integer, foreignTable: ask_user, foreignReference: id } created_at: ~ ask_relevancy: _attributes: { phpName: Relevancy, idMethod: native } answer_id: { type: integer, foreignTable: ask_answer, foreignReference: id } user_id: { type: integer, foreignTable: ask_user, foreignReference: id } score: { type: integer } created_at: ~ 构建对象模型 要使用InonoDB引擎,必须在askeet/config/目录下的propel.ini文件中加入下面一行: propel.mysql.tableType = InnoDB 一旦构建了schema.xml文件,我们可以基于这个关系模型生成一个对象模型。在Symfony中,对像关系映射是由Propel来处理的,但是封装到Symfony命令中: $ symfony propel-build-model 这 个命令生成(我们需要在askeet工程的根目录下调用)与schema中定义的表相对应的类,并带有标准的访问方法(->get()与-> set()方法)。我们可以在askeet/lib/model/om/目录下查看这些生成的代码。如果我们想知道对于每一个数据表为什么需要两个类,我 们可以查看Symfony一书的model一章。每次我们执行build-model时,这些类就会被覆盖,而这在这个工程中是经常发生的。所以如果我们 需要在模型对象添加方法,我们必须修改位于askeet/lib/model/目录下的类--他们由/om继承来的。 数据库 连接 现在数据库拥有一个数据库模型,现在我们要将工程连接到MySQL数据库。首先,我们需要在MySQL中创建一个数据库: $ mysqladmin -u youruser -p create askeet 现 在打开askeet/config/databases.yml配置文件。如果这是我们第一次使用Symfony,我们就会发现Symfony的配置文件 是使用YAML语法编写的。这个语法非常简单,但是却有一个约定:不可以使用tab,只可以使用空格。我们知道了这些,我们就可以编写文件,在all:类 别下添加到我们数据库的连接设置: all: propel: class: sfPropelDatabase param: phptype: mysql host: localhost database: askeet username: youruser password: yourpasswd 如果我们希望了解更多的关于Symfony配置与YAML文件的内容,我们可以查看Symfony一书实际配置一章。 构建 如果我们没有手工编写schema.yml文件,而在我们的数据库中已有相应的数据表,我们可以跳过这一部分。 对于键盘迷的我们来说,这里是一个叫人惊奇的地方:我们并不需要在MySQL数据库创建数据表与相应的数据列。我们已经在schema.xml中完成了工作,所以Symfony会为我们创建所有的SQL构建语句: $ symfony propel-build-sql 这个命令会在askeet/data/sql/目录下创建一个lib.model.schema.sql文件。在MySQL中将其作为一条SQL命令: $ mysql -u youruser -p askeet < data/sql/lib.model.schema.sql 相应的,我们也可以使用propel-insert-sql任务: $ symfony propel-insert-sql 通过CRUD测试数据访问 现在可以测试这些工作是否可以正常工作了。到现在为止,我们的浏览器还没有任何作用,而我们还在希望创建一个web程序。所以让我们来创建一个基本的Symfony模板与动作集合来处理'question'数据表的数据。这可以允许我们创建一些问题一些问题并进行显示。 在askeet/目录下,输入下面命令: $ symfony propel-generate-crud frontend question Question 这 会在frontend程序中为一个question模块生成一个框架,这个框架基于Question Propel对象模型,并且具有基本的创建,取出,更新与删除动作。不要感到迷惑:一个框架并不是一个完成的程序,而只是一个基本结构,在其上我们可以开 发新的特性,添加业务规则,自定义显示等。 由一个CRUD生成器创建的动作列表如下: 名字 描述 list 显示一个数据表的所有记录 index 转向到list show 显示一个指定记录的所有数据域 edit 显示一个表单来创建一个新的记录或是编辑一个已存在的记录 update 通过在请求中指定的参数来修改一个记录,然后转向到show delete 从数据表中删除一个指定的记录 我们可以在Symfony一书的框架一节了解到更多的关于生成的动作的内容。 无论何时当我们添加一个需要自动装入的新类时,不要忘记清除配置缓存(来重新装入自动载入缓存): $ symfony cc frontend config 现在我们可以通过下面的URL来进行在线测试了: http://askeet/question 现在我们可以执行一些操作。添加一些问题,编辑他们,删除他们。如果可以正常工作,这就意味着对象模型是正确的,也就是说到数据库的连接是正确的,数据库的关系模型到数据库的对象模型是正确的。这就是一个良好的功能测试。 明天见 我们并没有编写一行PHP代码,而我们已经有了一个可用的基本程序。这对于第二天并不坏。明天,我们将会编写一些代码来创建一个来显示问题列表的欢迎首页。我们也可以使用一个处理操作来向我们的数据库添加数据,并且学习如何扩展模块。 |
原文地址 http://www.symfony-project.com