Development

Documentation/zh_CN/jobeet/1.2/03 (diff)

You must first sign up to be able to contribute.

Changes from Version 1 of Documentation/zh_CN/jobeet/1.2/03

Show
Ignore:
Author:
sinosmond (IP: 123.120.172.42)
Timestamp:
04/14/09 14:32:35 (9 years ago)
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • Documentation/zh_CN/jobeet/1.2/03

    v0 v1  
     1{{{ 
     2#!WikiMarkdown 
     3 
     4第三天:数据模型 
     5===================== 
     6 
     7迫不及待的要操起编辑器编写 PHP 代码的朋友们今天一定会非常高兴,从今天起我们的教 
     8程终于要开始做一些开发工作了。我们要定义 Jobeet 的数据模型,使用 ORM 和数据库打 
     9交道并创建应用程序的第一个模块。 但是因为 symfony 已经为我们做了不少工作,我们 
     10不需要编写多少代码就可以拥有一个能完善工作的 web 模块了。 
     11 
     12<doctrine> 
     13启用 `sfDoctrinePlugin` 
     14--------------------------- 
     15 
     16如果你读到这段,那么你已经决定使用 ~Doctrine~ ~ORM~ 替换 Propel 来完成 Jobeet  
     17教程了。为此,你要做的第一件简单的事情就是启用 `sfDoctrinePlugin` 插件并关闭 
     18`sf#PropelPlugin` 插件。这可以通过修改 `config/ProjectConfiguration.class.php`  
     19文件完成。 
     20 
     21    [php] 
     22    public function setup() 
     23    { 
     24      $this->enablePlugins(array('sfDoctrinePlugin')); 
     25      $this->disablePlugins(array('sf#PropelPlugin')); 
     26    } 
     27 
     28如果你喜欢默认同时开启所有插件, 可以使用下面的配置: 
     29 
     30    [php] 
     31    public function setup() 
     32    { 
     33      $this->enableAllPluginsExcept(array('sf#PropelPlugin', 'sfCompat10Plugin')); 
     34    } 
     35 
     36>**NOTE** 
     37>在你做了这项修改之后,直到在稍后讲述的在 
     38>`config/~database~s.yml` 文件中配置 `sfDoctrineDatabase` 之前都会得到一个错误。 
     39 
     40做了这个修改之后确保一定要清空缓存。 
     41 
     42    $ php symfony cc 
     43 
     44正如本教程后面讲到的,每个插件都能嵌入它自己的 ~资产(asset)~ (包括 images, 
     45stylesheets 和 JavaScripts)。当安装或开启一个插件后,应该通过 `plugin:publish-assets`  
     46任务安装他们: 
     47 
     48    $ php symfony plugin:publish-assets 
     49 
     50我们还需要删除 `web/sf#PropelPlugin` 目录: 
     51 
     52    $ rm web/sf#PropelPlugin 
     53 
     54>**TIP** 
     55>使用 Doctrine 替换 Propel 之后的另一个建议是删除文件 
     56>`config/propel.ini` 和 `config/schema.yml` , 
     57>这样你就有了一个不涉及 Propel 的干净的安装。 
     58> 
     59>     $ rm config/propel.ini 
     60>     $ rm config/schema.yml 
     61</doctrine> 
     62 
     63关系~模型~ 
     64-------------------- 
     65 
     66昨天我们编写的用户故事提到了项目中的几个主要对象: 工作、联营用户和分类。下面是 
     67相应的实体关系图: 
     68 
     69![实体关系图](http://www.symfony-project.org/images/jobeet/1_2/03/diagram.png) 
     70 
     71除在故事中提到的字段外,我们还为一些表加入了 `created_at` 字段。 symfony 会 
     72自动识别这样的字段并在插入记录时设置其值为系统当前时间。`updated_at` 字段 
     73也一样,将在记录更新的时候设置其值为系统当前时间。 
     74 
     75~数据库设计(Schema)~ 
     76---------- 
     77 
     78显然我们需要关系型数据库来存储工作、联营用户和分类等数据。 
     79 
     80但是 symfony 作为一个面向对象的框架, 我们希望在尽可能的地方都可以操作~对象~。 
     81例如,我们更希望使用对象操作而不是编写 SQL 语句来获取数据库记录。 
     82 
     83关系数据库的信息必须被映射到对象模型上。这可以通过 
     84[ORM 工具](http://en.wikipedia.org/wiki/Object-relational_mapping)做到。 
     85谢天谢地,symfony 绑定了其中两个:[Propel](http://propel.phpdb.org/) 和 [Doctrine](http://www.doctrine-project.org/)。 
     86在本教程中,我们使用 ##ORM##。 
     87 
     88ORM 需要数据表信息及其依赖关系来创建相关的类。有两种方式来描述 schema: 对 
     89已有数据库的内省(introspecting)或者手工创建。 
     90 
     91<propel> 
     92>**Note** 
     93>有些工具允许你在可视化环境下创建数据库(例如 [Fabforce's Dbdesigner](http://www.fabforce.net/dbdesigner4/)) 
     94>并直接生成 `schema.xml` (可用 [DB Designer 4 TO Propel Schema Converter](http://blog.tooleshed.com/docs/dbd2propel/transform.php))。 
     95 
     96因为数据库尚不存在而且 Jobeet 数据库的结构也暂时不是很明朗,让我们通过 
     97手工方式创建 schema 文件吧。 
     98 
     99编辑空文件 `config/schema.yml`: 
     100 
     101    [yml] 
     102    # config/schema.yml 
     103    propel: 
     104      jobeet_category: 
     105        id:           ~ 
     106        name:         { type: varchar(255), required: true, index: unique } 
     107 
     108      jobeet_job: 
     109        id:           ~ 
     110        category_id:  { type: integer, foreignTable: jobeet_category, 
     111          ➥ foreignReference: id, required: true } 
     112        type:         { type: varchar(255) } 
     113        company:      { type: varchar(255), required: true } 
     114        logo:         { type: varchar(255) } 
     115        url:          { type: varchar(255) } 
     116        position:     { type: varchar(255), required: true } 
     117        location:     { type: varchar(255), required: true } 
     118        description:  { type: longvarchar, required: true } 
     119        how_to_apply: { type: longvarchar, required: true } 
     120        token:        { type: varchar(255), required: true, index: unique } 
     121        is_public:    { type: boolean, required: true, default: 1 } 
     122        is_activated: { type: boolean, required: true, default: 0 } 
     123        email:        { type: varchar(255), required: true } 
     124        expires_at:   { type: timestamp, required: true } 
     125        created_at:   ~ 
     126        updated_at:   ~ 
     127 
     128      jobeet_affiliate: 
     129        id:           ~ 
     130        url:          { type: varchar(255), required: true } 
     131        email:        { type: varchar(255), required: true, index: unique } 
     132        token:        { type: varchar(255), required: true } 
     133        is_active:    { type: boolean, required: true, default: 0 } 
     134        created_at:   ~ 
     135 
     136      jobeet_category_affiliate: 
     137        category_id:  { type: integer, foreignTable: jobeet_category, 
     138          ➥ foreignReference: id, required: true, primaryKey: true, 
     139          ➥ onDelete: cascade } 
     140        affiliate_id: { type: integer, foreignTable: jobeet_affiliate, 
     141          ➥ foreignReference: id, required: true, primaryKey: true, 
     142          ➥ onDelete: cascade } 
     143</propel> 
     144<doctrine> 
     145因为数据库尚不存在并且我们仍想保持 Jobeet 数据库的不可知性, 
     146让我们通过手工创建并编辑空的 schema 文件 
     147`config/doctrine/schema.yml` 来实现: 
     148 
     149>**TIP** 
     150>你需要在项目中手工创建 `config/doctrine/` 目录,因为它还不存在: 
     151> 
     152>     $ mkdir config/doctrine 
     153 
     154    [yml] 
     155    # config/doctrine/schema.yml 
     156    --- 
     157    JobeetCategory: 
     158      actAs: { Timestampable: ~ } 
     159      columns: 
     160        name: { type: string(255), notnull: true, unique: true } 
     161 
     162    JobeetJob: 
     163      actAs: { Timestampable: ~ } 
     164      columns: 
     165        category_id:  { type: integer, notnull: true } 
     166        type:         { type: string(255) } 
     167        company:      { type: string(255), notnull: true } 
     168        logo:         { type: string(255) } 
     169        url:          { type: string(255) } 
     170        position:     { type: string(255), notnull: true } 
     171        location:     { type: string(255), notnull: true } 
     172        description:  { type: string(4000), notnull: true } 
     173        how_to_apply: { type: string(4000), notnull: true } 
     174        token:        { type: string(255), notnull: true, unique: true } 
     175        is_public:    { type: boolean, notnull: true, default: 1 } 
     176        is_activated: { type: boolean, notnull: true, default: 0 } 
     177        email:        { type: string(255), notnull: true } 
     178        expires_at:   { type: timestamp, notnull: true } 
     179      relations: 
     180        JobeetCategory: { local: category_id, foreign: id, foreignAlias: JobeetJobs }  
     181 
     182    JobeetAffiliate: 
     183      actAs: { Timestampable: ~ } 
     184      columns: 
     185        url:       { type: string(255), notnull: true } 
     186        email:     { type: string(255), notnull: true, unique: true } 
     187        token:     { type: string(255), notnull: true } 
     188        is_active: { type: boolean, notnull: true, default: 0 } 
     189      relations: 
     190        JobeetCategories: 
     191          class: JobeetCategory 
     192          refClass: JobeetCategoryAffiliate 
     193          local: affiliate_id 
     194          foreign: category_id 
     195          foreignAlias: JobeetAffiliates 
     196 
     197    JobeetCategoryAffiliate: 
     198      columns: 
     199        category_id:  { type: integer, primary: true } 
     200        affiliate_id: { type: integer, primary: true } 
     201      relations: 
     202        JobeetCategory:  { onDelete: CASCADE, local: category_id, foreign: id } 
     203        JobeetAffiliate: { onDelete: CASCADE, local: affiliate_id, foreign: id } 
     204</doctrine> 
     205 
     206>**TIP** 
     207>如果你决定通过编写 SQL 语句来创建表,可以通过执行 `propel:build-schema` 任务 
     208>生成相应的 `schema.yml` 配置文件。 
     209> 
     210>     $ php symfony propel:build-schema 
     211> 
     212>上面的任务要求你已经在 `databases.yml` 中配置了数据库。我们将在稍后的步骤中 
     213>向你展示如何配置数据库。 如果你现在试着运行这个任务,它还不能运行因为它还不知道 
     214>此 schema 是属于哪个数据库的。 
     215 
     216schema 是实体关系图的 YAML 格式的直接翻译。 
     217 
     218>**SIDEBAR** 
     219>~YAML~ 文档格式 
     220> 
     221>根据 [YAML](http://yaml.org/) 官方网站资料, YAML 的意思是 “一种面向所有编程语言的 
     222>对人友好的数据序列化标准”。 
     223> 
     224>另一方面来说, YAML 是描述数据的简单语言 (strings, integers, dates, arrays, 和 hashes)。 
     225> 
     226>YAML 中,结构通过缩进来表示,连续的项目通过短横线“-”表示,哈希中的键值对通过冒号“:”分割。 
     227>YAML 还有用来描述好几行相同结构数据的缩写语法,数组用 `[]` 括起来,哈希用 `{}` 括起来。 
     228> 
     229>如果你还不熟悉 YAML 格式,那么该开始熟悉它了,因为 symfony 框架中广泛采用 YAML 格式 
     230>文档来编写配置文件。 
     231> 
     232>在编辑 YAML 文件时你需要牢记一点: 
     233>**缩进必须使用一个或多个空格,但是不能使用制表符**。 
     234 
     235`schema.yml` 文件包含了所有的表和字段的描述信息。每个字段都通过如下信息描述: 
     236 
     237<propel> 
     238  * `type`: ~字段类型~ (`boolean`, `tinyint`, `smallint`, `integer`, 
     239            `bigint`, `double`, `float`, `real`, `decimal`, `char`, 
     240            `varchar(size)`, `longvarchar`, `date`, `time`, `timestamp`, 
     241            `blob` 和 `clob`) 
     242  * `required`: 设为 `true` 表示必须填写该字段 
     243  * `~index~`: 设为 `true` 为该字段创建索引,或者设为 `unique` 在该字段上创建唯一索引。 
     244  * `primaryKey`: 定义表中的此字段为 ~主键(primary key)~ 。 
     245  * `foreignTable`, `foreignReference`: 定义此字段为另一个表的 ~外键(foreign key)~ 。 
     246         
     247设置为 `~` 的字段,在 YAML 中意为 `null` (`id`, `created_at`, 和 `updated_at`), 
     248symfony 会探测最适合的配置(`id`是主键字段,`created_at` 和 `updated_at`是时间戳....)。 
     249 
     250>**NOTE** 
     251>`onDelete` 属性定义了外键的 `ON DELETE` ~行为~。Propel 支持 `CASCADE`, `SETNULL`, 
     252>和 `RESTRICT`。例如,删除一条 `job` 记录后,`jobeet_job_affiliate` 中所有相关记录 
     253>也会自动通过数据库删除。如果底层的数据库引擎不支持该功能,Propel 可以做到。 
     254</propel> 
     255<doctrine> 
     256  * `type`: ~字段类型~ (`boolean`, `integer`, `float`, `decimal`, 
     257            `string`, `array`, `object`, `blob`, `clob`, `timestamp`, 
     258            `time`, `date`, `enum` 和 `gzip`)。 
     259  * `notnull`: 设为 `true` 表示必须填写该字段。 
     260  * `unique`: 设为 `true` 表示在该字段上创建唯一索引。 
     261 
     262>**NOTE** 
     263>`onDelete` 属性定义了外键的 `ON DELETE` ~行为~。Doctrine 支持 `CASCADE`, `SETNULL`, 
     264>和 `RESTRICT`。例如,删除一条 `job` 记录后,`jobeet_job_affiliate` 中所有相关记录 
     265>也会自动通过数据库删除。 
     266</doctrine> 
     267 
     268 
     269~数据库~ 
     270------- 
     271 
     272symfony 框架支持所有支持 PDO 的数据库(MySQL, PostgreSQL, SQLite, Oracle, MSSQL, ...)。 
     273[~PDO~](http://www.php.net/PDO)是 PHP 自带的~数据库抽象层~。 
     274 
     275这个教程我们就以 ~MySQL~ 为例吧: 
     276 
     277    $ mysqladmin -uroot -p create jobeet 
     278    Enter password: mYsEcret ## 口令将回显为 ******** 
     279 
     280>**Note** 
     281>如果你愿意可以随意选择另外一个~数据库引擎~,编写适应它的代码并不困难, 
     282>因为我们会使用 ORM 为我们编写 SQL 代码。 
     283 
     284我们需要通知 symfony 为 Jobeet 项目使用这个数据库: 
     285 
     286<propel> 
     287    $ php symfony configure:database 
     288      ➥ "mysql:host=localhost;dbname=jobeet" root mYsEcret 
     289</propel> 
     290<doctrine> 
     291默认的 `config/~databases.yml~` 包含一个涉及 propel 的连接。 
     292因为我们使用 Doctrine,所以我们必须先删除 `config/databases.yml`,  
     293然后为 Doctrine 重新生成它。 
     294 
     295    $ rm config/databases.yml 
     296 
     297现在简单地运行下面的命令为 Doctrine 生成一个新的数据库配置文件: 
     298 
     299    $ php symfony configure:database --name=doctrine 
     300      ➥ --class=sfDoctrineDatabase 
     301      ➥ "mysql:host=localhost;dbname=jobeet" root mYsEcret 
     302</doctrine> 
     303 
     304任务 `configure:database` 有三个参数: [PDO DSN](http://www.php.net/manual/en/pdo.drivers.php)、 
     305访问数据库的用户名和口令。如果你的开发服务器没有设置口令,可省略口令。 
     306 
     307>**NOTE** 
     308>任务 `configure:database` 可将数据库配置信息保存到配置文件 `config/databases.yml` 里。 
     309>你也可以不运行命令,而直接手动修改该文件。 
     310 
     311- 
     312 
     313>**CAUTION** 
     314>在命令行上直接输入数据库的口令虽然方便但是 
     315>[不安全](http://dev.mysql.com/doc/refman/5.1/en/password-security.html)。 
     316>根据访问你的环境的人员编辑 `config/databases.yml` 文件改变口令是更好的选择。 
     317>当然为了保证口令的安全,必须严格设置这个配置文件的访问权限。 
     318 
     319 
     320ORM 
     321------- 
     322 
     323要感谢在 `schema.yml` 文件中对数据库的描述,我们可以用 ##ORM## 内置的任务来生成 
     324~SQL~ 语句创建数据库中的表: 
     325 
     326<doctrine> 
     327为了生成 SQL 语句必须先从你的 schema 文件创建模型。 
     328 
     329    $ php symfony doctrine:build-model 
     330 
     331既然模型已经准备好了,你现在就能生成并插入 SQL 语句了。 
     332</doctrine> 
     333 
     334    $ php symfony propel:build-sql 
     335 
     336任务 `propel:build-sql` 在 `data/sql/`目录生成 SQL 语句,并针对我们配置的数据库 
     337引擎做了适当优化: 
     338 
     339<propel> 
     340    [sql] 
     341    # snippet from data/sql/lib.model.schema.sql 
     342    CREATE TABLE `jobeet_category` 
     343    ( 
     344            `id` INTEGER  NOT NULL AUTO_INCREMENT, 
     345            `name` VARCHAR(255)  NOT NULL, 
     346            PRIMARY KEY (`id`), 
     347            UNIQUE KEY `jobeet_category_U_1` (`name`) 
     348    )Type=InnoDB; 
     349</propel> 
     350<doctrine> 
     351    [sql] 
     352    # snippet from data/sql/schema.sql 
     353    CREATE TABLE jobeet_category (id BIGINT AUTO_INCREMENT, name VARCHAR(255) 
     354    NOT NULL COMMENT 'test', created_at DATETIME, updated_at DATETIME, slug 
     355    VARCHAR(255), UNIQUE INDEX sluggable_idx (slug), PRIMARY KEY(id)) 
     356    ENGINE = INNODB; 
     357</doctrine> 
     358 
     359运行 `propel:insert-sql` 任务可以在数据库中创建表: 
     360 
     361    $ php symfony propel:insert-sql 
     362 
     363 
     364<propel> 
     365因为该任务会删除原表再重新创建,此操作会提醒你确认后执行。你也可以 
     366用 `--no-confirmation` 参数忽略此提醒,这在非交互的批处理文件中 
     367运行此任务时非常有用: 
     368 
     369    $ php symfony propel:insert-sql --no-confirmation 
     370</propel> 
     371         
     372>**TIP** 
     373>和任何~命令行~工具一样,symfony 可以接受参数和选项。每个命令都有内置的帮助信息。 
     374>你可以通过运行 `help` 命令来查看: 
     375> 
     376>     $ php symfony help propel:insert-sql 
     377> 
     378>帮助信息列出了所有可能的参数和选项,以及他们的默认值,并给出了一些很有用的示例。 
     379 
     380 
     381ORM 还可以生成映射数据表和对象间关系的 PHP 类: 
     382 
     383    $ php symfony propel:build-model 
     384 
     385`propel:build-model` 任务在 `lib/model/` 目录生成用于和数据库交互的 PHP 文件。 
     386 
     387<propel> 
     388浏览生成的文件,你可能会发现 Propel 针对每个~表~都生成了四个类,例如 `jobeet_job` 表: 
     389 
     390  * `JobeetJob`: 此类中的对象代表 `jobeet_job` **表中的一条~记录~**。此类默认为空。 
     391  * `BaseJobeetJob`: `JobeetJob` 类的父类。 
     392                     每当运行 `propel:build-model` 任务时,这个类都会被覆盖。 
     393                                 因此,所有的自定义操作都必须在 `JobeetJob` 类中编写。 
     394  
     395  * `JobeetJobPeer`: 此类定义了一系列的静态方法,大多都是返回一系列 `JobeetJob` 对象的。 
     396                     此类默认为空。 
     397  * `BaseJobeetJobPeer`: `JobeetJobPeer` 的父类。 
     398                         每当运行 `propel:build-model` 任务时这个类都会被覆盖。 
     399                                                 因此,所有的自定义操作都必须在 `JobeetJobPeer` 类中编写。 
     400</propel> 
     401<doctrine> 
     402浏览生成的文件,你可能会发现 Doctrine 针对每个~表~都生成了三个类,例如 `jobeet_job` 表:  
     403 
     404 * `JobeetJob`: 此类中的对象代表 `jobeet_job` **表中的一条~记录~**。此类默认为空。 
     405 * `BaseJobeetJob`: `JobeetJob` 类的父类。  
     406                     每当运行 `doctrine:build-model` 任务时,这个类都会被覆盖。 
     407                                 因此,所有的~自定义~操作都必须在 `JobeetJob` 类中编写。 
     408 
     409 * `JobeetJobTable`: 此类定义的方法主要返回 `JobeetJob` 对象的方法。(The class defines methods that mostly return collections of `JobeetJob` objects.) 此类默认为空。 
     410</doctrine> 
     411 
     412- 
     413 
     414通过模型对象操作记录时,可以通过获取方法(`get*()` 类方法)和设置方法(`set*()` 类方法)来操作字段的值: 
     415 
     416    [php] 
     417    $job = new JobeetJob(); 
     418    $job->setPosition('Web developer'); 
     419    $job->save(); 
     420 
     421    echo $job->getPosition(); 
     422 
     423    $job->delete(); 
     424 
     425还可以直接通过链接对象来定义~外键~。 
     426 
     427    [php] 
     428    $category = new JobeetCategory(); 
     429    $category->setName('Programming'); 
     430 
     431    $job = new JobeetJob(); 
     432    $job->setCategory($category); 
     433 
     434`propel:build-all` 任务是本小节及以后都会运行的快捷方式。现在,运行此命令为 
     435我们的 Jobeet 模型类生成表单和验证器吧。 
     436 
     437    $ php symfony propel:build-all --no-confirmation 
     438         
     439你会在今天的最后看到验证器,表单则会在第10天的教程中详细讲解其美妙的细节。 
     440 
     441在后面你可以发现,symfony 会为你~自动加载~ PHP 类文件,这意味着你不再需要在 
     442代码中写 `require` 语句了。这是 symfony 自动为开发者做处理的杂事儿中的一点。 
     443但这也有不好的一面,每新增一个类都得清空一下 symfony 的~缓存~。 
     444由于 `propel:build-model` 任务创建了许多新类,让我们先清理缓存: 
     445 
     446     $ php symfony cache:clear 
     447 
     448 
     449>**TIP** 
     450>symfony ~任务~ 由命名空间和任务名称组成。 只要不和其他命令冲突,都可以尽可能的简写。 
     451>因此,下面这些命令都等价于 `cache:clear` : 
     452> 
     453>     $ php symfony cache:cl 
     454>     $ php symfony ca:c 
     455> 
     456>因为 `cache:clear` 任务经常被使用,它有更简单的缩写形式: 
     457> 
     458>     $ php symfony cc 
     459 
     460 
     461初始化数据 
     462---------------- 
     463 
     464我们已经在数据库中创建了数据表,但还没有数据。作为 web 应用程序,有三种类型的数据: 
     465 
     466  * **初始化数据**: 是应用程序正常运行所必须的数据。例如。Jobeet 需要一些初始的分类。 
     467                    没有的话就没有人能发布工作。我们还需要一个可以登录到后台的管理员用户。 
     468  * **测试数据**: ~测试数据~ 用于测试应用程序。作为开发者,你需要编写测试用例以确保应用 
     469                  程序按照用户故事里预期的那样正常运行。最好的方式就是编写用例进行自动化 
     470                                  测试。每次运行测试用例的时候,都需要清空数据库并加载一些测试数据。 
     471  * **用户数据**: 用户数据是应用程序正常运行时用户创建的数据。 
     472 
     473每一次 symfony 创建数据库表结构时,所有的数据都会丢失。要向数据库中填充初始化数据, 
     474我们可以编写 PHP 脚本或者通过 `mysql` 程序运行一些 SQL 语句。由于要经常用到, symfony  
     475里面有更好的方式:在 `data/fixtures/` 目录下创建 YAML 文件,然后用 `propel:data-load`  
     476任务加载到数据库中: 
     477 
     478首先创建如下的 ~fixtures~ 文件: 
     479 
     480<propel> 
     481    [yml] 
     482    # data/fixtures/010_categories.yml 
     483    JobeetCategory: 
     484      design:        { name: Design } 
     485      programming:   { name: Programming } 
     486      manager:       { name: Manager } 
     487      administrator: { name: Administrator } 
     488 
     489    # data/fixtures/020_jobs.yml 
     490    JobeetJob: 
     491      job_sensio_labs: 
     492        category_id:  programming 
     493        type:         full-time 
     494        company:      Sensio Labs 
     495        logo:         sensio-labs.gif 
     496        url:          http://www.sensiolabs.com/ 
     497        position:     Web Developer 
     498        location:     Paris, France 
     499        description:  | 
     500          You've already developed websites with symfony and you want to 
     501          work with Open-Source technologies. You have a minimum of 3 
     502          years experience in web development with PHP or Java and you 
     503          wish to participate to development of Web 2.0 sites using the 
     504          best frameworks available. 
     505        how_to_apply: | 
     506          Send your resume to fabien.potencier [at] sensio.com 
     507        is_public:    true 
     508        is_activated: true 
     509        token:        job_sensio_labs 
     510        email:        job@example.com 
     511        expires_at:   2010-10-10 
     512 
     513      job_extreme_sensio: 
     514        category_id:  design 
     515        type:         part-time 
     516        company:      Extreme Sensio 
     517        logo:         extreme-sensio.gif 
     518        url:          http://www.extreme-sensio.com/ 
     519        position:     Web Designer 
     520        location:     Paris, France 
     521        description:  | 
     522          Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do 
     523          eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut 
     524          enim ad minim veniam, quis nostrud exercitation ullamco laboris 
     525          nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor 
     526          in reprehenderit in. 
     527 
     528          Voluptate velit esse cillum dolore eu fugiat nulla pariatur. 
     529          Excepteur sint occaecat cupidatat non proident, sunt in culpa 
     530          qui officia deserunt mollit anim id est laborum. 
     531        how_to_apply: | 
     532          Send your resume to fabien.potencier [at] sensio.com 
     533        is_public:    true 
     534        is_activated: true 
     535        token:        job_extreme_sensio 
     536        email:        job@example.com 
     537        expires_at:   2010-10-10 
     538</propel> 
     539<doctrine> 
     540    [yml] 
     541    # data/fixtures/categories.yml 
     542    JobeetCategory: 
     543      design: 
     544        name: Design 
     545      programming: 
     546        name: Programming 
     547      manager: 
     548        name: Manager 
     549      administrator: 
     550        name: Administrator 
     551 
     552    # data/fixtures/jobs.yml 
     553    JobeetJob: 
     554      job_sensio_labs: 
     555        JobeetCategory: programming 
     556        type:         full-time 
     557        company:      Sensio Labs 
     558        logo:         sensio-labs.gif 
     559        url:          http://www.sensiolabs.com/ 
     560        position:     Web Developer 
     561        location:     Paris, France 
     562        description:  | 
     563          You've already developed websites with symfony and you want to work 
     564          with Open-Source technologies. You have a minimum of 3 years 
     565          experience in web development with PHP or Java and you wish to 
     566          participate to development of Web 2.0 sites using the best 
     567          frameworks available. 
     568        how_to_apply: | 
     569          Send your resume to fabien.potencier [at] sensio.com 
     570        is_public:    true 
     571        is_activated: true 
     572        token:        job_sensio_labs 
     573        email:        job@example.com 
     574        expires_at:   '2010-10-10' 
     575 
     576      job_extreme_sensio: 
     577        JobeetCategory:  design 
     578        type:         part-time 
     579        company:      Extreme Sensio 
     580        logo:         extreme-sensio.gif 
     581        url:          http://www.extreme-sensio.com/ 
     582        position:     Web Designer 
     583        location:     Paris, France 
     584        description:  | 
     585          Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do 
     586          eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut 
     587          enim ad minim veniam, quis nostrud exercitation ullamco laboris 
     588          nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor 
     589          in reprehenderit in. 
     590 
     591          Voluptate velit esse cillum dolore eu fugiat nulla pariatur. 
     592          Excepteur sint occaecat cupidatat non proident, sunt in culpa 
     593          qui officia deserunt mollit anim id est laborum. 
     594        how_to_apply: | 
     595          Send your resume to fabien.potencier [at] sensio.com 
     596        is_public:    true 
     597        is_activated: true 
     598        token:        job_extreme_sensio 
     599        email:        job@example.com 
     600        expires_at:   '2010-10-10' 
     601</doctrine> 
     602 
     603>**NOTE** 
     604>这个 fixture 文件用到了两个图片文件。你可以下载  
     605>(`http://www.symfony-project.org/get/jobeet/sensio-labs.gif`, 
     606>`http://www.symfony-project.org/get/jobeet/extreme-sensio.gif`)  
     607>然后将他们放到 `uploads/jobs/` 目录下。 
     608 
     609fixtures 文件用 YAML 格式编写,定义了模型对象并用唯一的名字打上了标签 
     610(例如:我们定义了 `job_sensio_labs` 和 `job_extreme_sensio` 两个标签)。 
     611这些标签让我们不必定义~主键~(这通常是自增的也没办法预先设定的)就可以 
     612链接关联对象。例如 `job_sensio_labs` 工作的分类是 `programming`, 
     613它是 'Programming' 分类的标签。 
     614 
     615>**TIP** 
     616>YAML 文件中,当一个字符串要跨越多行时(例如 job fixture 文件中的 
     617>`description` 字段), 你可以使用管道符 (`|`) 指明字符串要跨越多行。 
     618 
     619虽然一个 fixture 文件可以包含一个或多个模型中的对象,我们还是决定在 
     620Jobeet 的 fixture 中为每个模型创建一个文件。 
     621 
     622<propel> 
     623>**TIP** 
     624>注意文件名中的前缀数字,这是控制数据加载先后顺序的简单方法。 
     625>在本项目后期,如果我们需要插入一些新的 fixture,可以简单地使用已存在的数字之间的预留数字前缀。 
     626>因为其间有一些预留的数字前缀可用(例如 011 到 019)。 
     627</propel> 
     628<doctrine> 
     629>**NOTE** 
     630>Propel 要求在 fixture 文件名使用数字前缀来决定文件的加载顺序。 
     631>对 Doctrine 而言这不是必须的,因为所有的 fixture 都会被加载 
     632>并且保存为确保外键正确设置的正确的顺序。 
     633</doctrine> 
     634 
     635在 fixtures 文件中,你不必定义所有字段的值。如果没有,symfony 会自动使用在数据库 
     636结构定义的默认值。因为 symfony 使用 ##ORM## 加载数据到数据库中,所有内置的行为 
     637(像自动设置的 `created_at` 和 `updated_at` 字段的值)或者你自己加入模型类中自 
     638定义行为都是可用的。 
     639 
     640只需简单的运行 `propel:data-load` 任务就可加载初始化数据到数据库中: 
     641 
     642    $ php symfony propel:data-load 
     643 
     644>**TIP** 
     645>`propel:build-all-load` 任务是 `propel:build-all` 任务以及紧随其后的 
     646>`propel:data-load` 任务的一个快捷方式。 
     647 
     648<doctrine> 
     649运行 `doctrine:build-all-reload` 任务要确认一切的一切都已经从你的 schema 生成了。 
     650这将会生成表单、过滤器、模型,删除数据库的所有表并重新生成他们。 
     651 
     652    $ php symfony doctrine:build-all-reload 
     653</doctrine> 
     654 
     655在浏览器中查看实际效果 
     656------------------------------- 
     657 
     658我们使用了很多的命令行操作,那一点也不让人激动,特别是对于 web 项目来说。 
     659现在我们万事具备只需要创建页面来和数据库交互。 
     660 
     661让我们看看怎样显示工作列表,怎样编辑已有工作以及怎样删除工作。 
     662正如第一天解释的那样,symfony 项目由应用程序组成。 
     663每个~应用程序~下面又包括若干个**模块**。 
     664模块是实现应用程序功能(如 API 模块)的一套自包含的 PHP 代码, 
     665也可以是用户针对模型可以做的一系列的操作的集合(例如工作模块)。 
     666 
     667symfony 能够根据模型自动创建模块,提供基本的操作功能。 
     668 
     669    $ php symfony propel:generate-module --with-show 
     670      ➥ --non-verbose-templates frontend job JobeetJob 
     671 
     672`propel:generate-module` 任务为工作模型 `JobeetJob` 在 `frontend` 应用程序中 
     673生成了名为 `job` 的模块。和大多数 symfony 命令一样,它在  
     674`apps/frontend/modules/job/` 下创建了一些文件和目录: 
     675 
     676 | 目录         | 说明 
     677 | ------------ | -------------------- 
     678 | `actions/`   | 模块的操作 
     679 | `templates/` | 模块的模板 
     680 
     681`actions/actions.class.php` 文件定义了 `job` 模块所有可用的**~动作~**: 
     682 
     683 | 动作名         | 说明 
     684 | -------------- | ------------------------------------------------------- 
     685 | `index`        | 显示数据表中的记录 
     686 | `show`         | 显示指定记录的字段及其值 
     687 | `new`          | 显示一个创建新记录的表单 
     688 | `create`       | 创建一条新的记录 
     689 | `edit`         | 显示修改一条已有记录的表单 
     690 | `update`       | 根据用户提交的数据更新记录 
     691 | `delete`       | 从数据表中删除指定的记录 
     692 
     693现在你可以在浏览器中测试工作模块: 
     694 
     695     http://jobeet.localhost/frontend_dev.php/job 
     696 
     697![工作模块](http://www.symfony-project.org/images/jobeet/1_2/03/job.png) 
     698 
     699<propel> 
     700如果你试图编辑一个工作,因为 symfony 需要分类的文字表示,这会触发一个异常。 
     701PHP 对象表示法可以通过定义魔术方法 `__toString()` 来实现。 
     702一条分类记录的文字表示应该在 `JobeetCategory` 模型类文件中定义: 
     703 
     704    [php] 
     705    // lib/model/JobeetCategory.php 
     706    class JobeetCategory extends BaseJobeetCategory 
     707    { 
     708      public function __toString() 
     709      { 
     710        return $this->getName(); 
     711      } 
     712    } 
     713 
     714现在每次 symfony 需要分类的文字表示时都会调用 `__toString()` 方法来返回分类的 
     715名称。因为我们差不多在所有的模型类中都要用到文字表示,因此让我们为每个类都定义 
     716一个 `__toString()` 方法: 
     717</propel> 
     718<doctrine> 
     719如果你试图编辑一个工作,你会注意到 Category id 的下拉菜单提供了所有的分类名称的列表。 
     720每个选项值都是从 `__toString()` 方法获得的。 
     721 
     722Doctrine 通过猜测一个列名的描述(如:`title`, `name`, `subject` 等) 
     723试着提供一个基本的 `~__toString()~` 方法。 
     724如果想要自定义,你必须像下面那样添加自己的 `__toString()` 方法。 
     725`JobeetCategory` 模块能够通过 `jobeet_category` 表使用的 `name` 列猜测 `__toString()` 方法。 
     726</doctrine> 
     727 
     728    [php] 
     729<propel> 
     730    // lib/model/JobeetJob.php 
     731</propel> 
     732<doctrine> 
     733    // lib/model/doctrine/JobeetJob.class.php 
     734</doctrine> 
     735    class JobeetJob extends BaseJobeetJob 
     736    { 
     737      public function __toString() 
     738      { 
     739        return sprintf('%s at %s (%s)', $this->getPosition(), 
     740         ➥ $this->getCompany(), $this->getLocation()); 
     741      } 
     742    } 
     743 
     744<propel> 
     745    // lib/model/JobeetAffiliate.php 
     746</propel> 
     747<doctrine> 
     748    // lib/model/doctrine/JobeetAffiliate.class.php 
     749</doctrine> 
     750    class JobeetAffiliate extends BaseJobeetAffiliate 
     751    { 
     752      public function __toString() 
     753      { 
     754        return $this->getUrl(); 
     755      } 
     756    } 
     757 
     758现在你可以创建和修改工作了。试着空缺必填字段或者填错日期看看。 
     759对,通过对数据库结构的内省,symfony 已经创建了基本的验证规则。 
     760 
     761![validation](http://www.symfony-project.org/images/jobeet/1_2/03/validation.png) 
     762 
     763 
     764明天见 
     765---------------- 
     766 
     767这就是今天的所有内容。在开始的介绍部分我就警告过你。今天,虽然我们编写了少量  
     768PHP 代码,但是工作模型已经有一个可用的模块了,还可以进行优化和定制。 
     769记住。没有 PHP 代码也就没有 BUG! 
     770 
     771如果还有精力的话不妨读读针对模块和模型生成的代码,并试图了解它们是如何工作的。 
     772如果不懂也别担心,好好睡一觉,明天我们就会讨论 web 框架最常用的原则, 
     773[MVC 设计模式](http://en.wikipedia.org/wiki/Model-view-controller)。 
     774 
     775和每天一样,今天编写的代码可以在 Jobeet SVN 代码仓库中获得。请使用 
     776`release_day_03` tag 检出代码: 
     777 
     778    $ svn co http://svn.jobeet.org/##ORM_LOWER##/tags/release_day_03/ jobeet/ 
     779 
     780__ORM__ 
     781 
     782}}}