最近一段时间比较空一些:受经济危机影响,来访的客户少了;太子期末考试结束,我也不用揪着他复习功课了。所以决定把Symfony看一看。
Symfony当前的版本是1.2,同时它提供了一个还是比较实用的Step by step教程来创建一个所谓的Job Board(教程的链接在这里——这个教程使用的数据库ORM是Propel,还有一个是基于Doctrine的,链接在这里)。这个教程的目的是在24天内,每天用差不多一个小时,总共24小时,来建立一个供用户发布、搜索、订阅工作机会以及关联网站共享工作机会信息的WEB平台。这个平台包括前后台,全部基于Symfony框架构建。 在整个24课时中,教程包含了很多重要的内容:
- 项目、应用的创建和数据模型(包括样本数据、测试数据)的建立;
- MVC结构,路由;
- 单元测试和功能测试;
- 表单的定制和测试;
- 后台管理模块;
- RSS Feed,Web Service;
- 基于Lucene的全文搜索;
- 基于jQuery的AJAX;
- i18n和l10n,cache,plugins;
- 发布
可以说,内容相当广泛,而且对于一个完整的web站点来说,这些功能也是非常基本的。所以,我决定使用Symfony来重新改写我的任氏有无轩。当然,我没有那么大的野心要在24小时内完成,只是希望能通过这个过程去充分掌握Symfony并交出一份比较合理的作业。
============
我的开发平台和实际的运行平台是不同的。先介绍一下这两个平台的相关配置:
- 开发平台:Windows Vista Business + XAMPP +PHPED + NetBeans IDE + SciTE。之所以要用那么多编辑器是因为我发现各有各的优缺点。有关的比较有空的话我再另文描述;
- 运行平台:Loongson 2F + LAMP。通过ssh进行远程控制和操作;
在重新开发过程中,原来的站点还将继续运作,直到开发完成。在这个过程中,我还要完成数据库的迁移(从InterBase到MySQL)。
这一系列的文章不会成为完整的一个语句接着一个语句的操作,而更注重在整个应用的开发过程。
==============
由于我目前的站点版面设计已经完成,我还想再用这个设计一段时间,而且总体模块也比较固定,所以,整个站点的框架已经基本确定了。我不会太多的改动这些东西。
好吧,让我开始!
==============
第一步当然是框架的安装。Symfony框架是非常自成体系的。所有的文件都包含在一个目录之中,要发布的话也只要简单拷贝即可。所以,我在开发机上进行了一些设置——具体的设置在教程的第一天中。
首先我要创建一个项目:
F:/www/books>php lib/vendor/symfony/data/bin/symfony generate:project books
然后是创建一个应用。一般而言,总是先创建前台,然后才是后台:
symfony generate:app --escaping-strategy=on --csrf-secret=123456 frontend
从这个命令中可以看出,symfony已经内置了防止XSS和CSRF的措施,我们要做的只是激活他们。
这两个命令会在当前目录下创建一系列的子目录和文件,都是框架本身的要求:
- /apps: 所有的应用文件都在这里;
- /cache: 缓存的文件;
- /config: 项目的配置文件。最重要的是:databases.yml,它保存了各个开发环境下(开发环境和测试环境)的数据库链接参数;schema.yml,它保存了数据库的schema;
- /lib: 项目的公用库和根据ORM而来的类文件;
- /log: 日志文件。在调试时会有一定的帮助;
- /plugins: 插件。这里可以存放插件用来扩展symfony;
- /test: 进行单元测试和功能测试的地方;
- /web: 真正应该访问到的web根目录,包含index.php,frontend_dev.php,robots.txt,.htaccess等文件。下面还包括css,images,js,uploads等目录,分别存放对应的支持文件。
symfony大量的使用了YAML作为其配置文件格式。作为我来说,我只是简单的将其认为是一个XML的简化版本就足够了。
这时我们已经可以访问localhost了。我们可以直接显示index.php,也可以访问frontend_dev.php:
这两个界面没有什么大区别,只是frontend_dev界面中多了一个调试工具栏可以让你看到页面执行的一些情况,特别是log和sql部分。
==============
接下来,我要开始编写我的数据库schema。在教程中,这个是第三天的任务。symfony支持两种方式:一种是从编写schema.yml开始,然后用build-sql、insert-sql任务来创建表;另一种是反过来,先在MySQL中定义好表和关联,然后用build-schema来创建schema.yml。这两种方式是等效的,看个人的喜好而定。我比较喜欢后一种。
于是我用PMA建立了最早期的四个表:book_book(存放书籍基本信息),book_publisher(出版社信息),book_place(买书地方的信息),book_taglist(存放书籍的tag)。这些表的结构是从原来的应用(桌面和WEB)继承过来的,我也不想再更改,否则工程量更大。
首先我要配置一下databases.yml文件,告诉它我的数据库链接参数:
symfony config:database mysql:host=localhost;dbname=books root 123456
然后运行build-schema任务:
symfony propel:build-schema
这样,我的databases.yml和schema.yml就更新了:
#config/databases.yml
dev:
propel:
param:
classname: DebugPDO
test:
propel:
param:
classname: DebugPDO
all:
propel:
class: sfPropelDatabase
param:
classname: PropelPDO
dsn: 'mysql:host=localhost;dbname=books'
username: root
password: 123456
encoding: utf8
persistent: true
pooling: true
#config/schema.yml
propel:
_attributes:
package: lib.model
defaultIdMethod: native
book_book:
_attributes: { phpName: BookBook }
id: { type: CHAR, size: '5', primaryKey: true, required: true }
title: { type: VARCHAR, size: '200', required: true }
author: { type: VARCHAR, size: '200', required: true }
region: { type: VARCHAR, size: '40', required: true }
copyrighter: { type: VARCHAR, size: '100', required: false }
translated: { type: TINYINT, size: '1', required: false, defaultValue: '0' }
place: { type: INTEGER, size: '11', required: false, defaultValue: '-1', foreignTable: book_place, foreignReference: id, onDelete: RESTRICT, onUpdate: CASCADE }
publisher: { type: INTEGER, size: '11', required: false, defaultValue: '-1', foreignTable: book_publisher, foreignReference: id, onDelete: RESTRICT, onUpdate: CASCADE }
purchdate: { type: DATE, required: false }
price: { type: FLOAT, required: false }
pubdate: { type: DATE, required: false }
printdate: { type: DATE, required: false }
ver: { type: CHAR, size: '5', required: false }
deco: { type: CHAR, size: '6', required: false }
kword: { type: INTEGER, size: '11', required: false }
page: { type: INTEGER, size: '11', required: false }
isbn: { type: CHAR, size: '13', required: false }
category: { type: VARCHAR, size: '8', required: false }
location: { type: CHAR, size: '2', required: false }
intro: { type: LONGVARCHAR, required: false }
instock: { type: TINYINT, size: '1', required: true, defaultValue: '1' }
_indexes: { publisher: [publisher], place: [place] }
_uniques: { uniquebook: [title, author, purchdate, ver] }
… …
我只列出了一部分。请注意主键、索引、复合索引的声明。这些,都使用YAML格式写成。
然后我要用build-model任务来完成ORM的工作:
ymfony propel:build-model
symfony会在lib/model下创建对应于这四个表的映射类。以book表为例,有四个文件:
- BookBook: 该类的一个实例将代表book_book表的一条记录。这个类缺省是空的;
- BaseBookBook: 是上面类的父类。每次运行
propel:build-model时,这个类就会重建,所以所有的定制应该在BookBook中进行; - BookBookPeer: 这个类中定义了一些静态方法,主要用来返回BookBook对象的集合。这个类缺省也是空的;
- BaseBookBookPeer: 是上面类的父类。每次运行
propel:build-model时,这个类就会重建,所以所有的定制应该在BookBookPeer中进行;
进行了这个building-model操作后,symfony创建了很多新的类。每次在symfony中创建新的类后,都要对缓存文件进行手工清理:
symfony cc
=============
接下来是样本数据的填充。symfony使用的方法很灵活,允许我们在yml中使用PHP基本结构,这样我们可以在短时间内创建大量实际有用的数据。例如:
#data/fixtures/030_book.yml
BookBook:
<?php for ($i = 1; $i <= 100; $i++): ?>
book_<?php echo $i ?>:
id: <?php echo $i.n ?>
title: book_<?php echo $i.n ?>
author: author_<?php echo $i.n ?>
region: region_<?php echo $i.n ?>
place: 1
publisher: 1
instock: true
<?php endfor; ?>
#data/fixtures/040_taglist.yml
BookTaglist:
<?php for ($i = 1; $i <= 100; $i++): ?>
tag_<?php echo $i ?>:
id: book_<?php echo $i ?>
tag: tag1_<?php echo $i ?>
<?php endfor; ?>
<?php for ($i = 1; $i <= 100; $i++): ?>
tag2_<?php echo $i ?>:
id: book_<?php echo $i ?>
tag: tag2_<?php echo $i ?>
<?php endfor; ?>
这里我只说明三个要点:
- data/fixtures目录下存放了样本数据的yml文件。各个文件的前缀010/020/030/040等代表了加载的顺序。这样我们就可以在填充样本数据的时候满足引用一致性了;
- 对于taglist表而言,它和book表是多对一的关系。我用了两个循环来为每本书加入两个tag。其中引用到的id并不是一个简单的数字或字符串,我直接引用了我在030中创建的book对象的示例。这样做的好处是对于那些自动增长的字段,我们可以暂时不关心其父表中的id到底是什么。symfony会自动获得;
- 请注意PHP代码的格式,它暂时脱离了缩进。另外,echo后面需要加个回车符号,这个是为了保证yml文件的格式。
然后我们通过data-load任务来填充数据:
symfony propel:data-load
再次进入PMA后,我们可以看到所有数据已经填充完毕。 好了,作为第一天,就到这里吧。
Leave a Reply to Going for Symfony | 第二天 | 生活在远方 Cancel reply