Development

Documentation/zh_CN/jobeet/1.2/03

You must first sign up to be able to contribute.

第三天:数据模型

迫不及待的要操起编辑器编写 PHP 代码的朋友们今天一定会非常高兴,从今天起我们的教 程终于要开始做一些开发工作了。我们要定义 Jobeet 的数据模型,使用 ORM 和数据库打 交道并创建应用程序的第一个模块。 但是因为 symfony 已经为我们做了不少工作,我们 不需要编写多少代码就可以拥有一个能完善工作的 web 模块了。

启用 sfDoctrinePlugin

如果你读到这段,那么你已经决定使用 ~Doctrine~ ~ORM~ 替换 Propel 来完成 Jobeet 教程了。为此,你要做的第一件简单的事情就是启用 sfDoctrinePlugin 插件并关闭 sf#PropelPlugin 插件。这可以通过修改 config/ProjectConfiguration.class.php 文件完成。

[php]
public function setup()
{
  $this->enablePlugins(array('sfDoctrinePlugin'));
  $this->disablePlugins(array('sf#PropelPlugin'));
}

如果你喜欢默认同时开启所有插件, 可以使用下面的配置:

[php]
public function setup()
{
  $this->enableAllPluginsExcept(array('sf#PropelPlugin', 'sfCompat10Plugin'));
}

NOTE 在你做了这项修改之后,直到在稍后讲述的在 config/~database~s.yml 文件中配置 sfDoctrineDatabase 之前都会得到一个错误。

做了这个修改之后确保一定要清空缓存。

$ php symfony cc

正如本教程后面讲到的,每个插件都能嵌入它自己的 ~资产(asset)~ (包括 images, stylesheets 和 JavaScripts)。当安装或开启一个插件后,应该通过 plugin:publish-assets 任务安装他们:

$ php symfony plugin:publish-assets

我们还需要删除 web/sf#PropelPlugin 目录:

$ rm web/sf#PropelPlugin

TIP 使用 Doctrine 替换 Propel 之后的另一个建议是删除文件 config/propel.iniconfig/schema.yml , 这样你就有了一个不涉及 Propel 的干净的安装。

$ rm config/propel.ini
$ rm config/schema.yml

关系~模型~

昨天我们编写的用户故事提到了项目中的几个主要对象: 工作、联营用户和分类。下面是 相应的实体关系图:

实体关系图

除在故事中提到的字段外,我们还为一些表加入了 created_at 字段。 symfony 会 自动识别这样的字段并在插入记录时设置其值为系统当前时间。updated_at 字段 也一样,将在记录更新的时候设置其值为系统当前时间。

~数据库设计(Schema)~

显然我们需要关系型数据库来存储工作、联营用户和分类等数据。

但是 symfony 作为一个面向对象的框架, 我们希望在尽可能的地方都可以操作~对象~。 例如,我们更希望使用对象操作而不是编写 SQL 语句来获取数据库记录。

关系数据库的信息必须被映射到对象模型上。这可以通过 ORM 工具做到。 谢天谢地,symfony 绑定了其中两个:PropelDoctrine。 在本教程中,我们使用 ##ORM##。

ORM 需要数据表信息及其依赖关系来创建相关的类。有两种方式来描述 schema: 对 已有数据库的内省(introspecting)或者手工创建。

>Note >有些工具允许你在可视化环境下创建数据库(例如 Fabforce's Dbdesigner) >并直接生成 schema.xml (可用 DB Designer 4 TO Propel Schema Converter)。

因为数据库尚不存在而且 Jobeet 数据库的结构也暂时不是很明朗,让我们通过 手工方式创建 schema 文件吧。

编辑空文件 config/schema.yml:

[yml]
# config/schema.yml
propel:
  jobeet_category:
    id:           ~
    name:         { type: varchar(255), required: true, index: unique }

  jobeet_job:
    id:           ~
    category_id:  { type: integer, foreignTable: jobeet_category,
      ➥ foreignReference: id, required: true }
    type:         { type: varchar(255) }
    company:      { type: varchar(255), required: true }
    logo:         { type: varchar(255) }
    url:          { type: varchar(255) }
    position:     { type: varchar(255), required: true }
    location:     { type: varchar(255), required: true }
    description:  { type: longvarchar, required: true }
    how_to_apply: { type: longvarchar, required: true }
    token:        { type: varchar(255), required: true, index: unique }
    is_public:    { type: boolean, required: true, default: 1 }
    is_activated: { type: boolean, required: true, default: 0 }
    email:        { type: varchar(255), required: true }
    expires_at:   { type: timestamp, required: true }
    created_at:   ~
    updated_at:   ~

  jobeet_affiliate:
    id:           ~
    url:          { type: varchar(255), required: true }
    email:        { type: varchar(255), required: true, index: unique }
    token:        { type: varchar(255), required: true }
    is_active:    { type: boolean, required: true, default: 0 }
    created_at:   ~

  jobeet_category_affiliate:
    category_id:  { type: integer, foreignTable: jobeet_category,
      ➥ foreignReference: id, required: true, primaryKey: true,
      ➥ onDelete: cascade }
    affiliate_id: { type: integer, foreignTable: jobeet_affiliate,
      ➥ foreignReference: id, required: true, primaryKey: true,
      ➥ onDelete: cascade }

因为数据库尚不存在并且我们仍想保持 Jobeet 数据库的不可知性, 让我们通过手工创建并编辑空的 schema 文件 config/doctrine/schema.yml 来实现:

TIP 你需要在项目中手工创建 config/doctrine/ 目录,因为它还不存在:

$ mkdir config/doctrine
[yml]
# config/doctrine/schema.yml
---
JobeetCategory:
  actAs: { Timestampable: ~ }
  columns:
    name: { type: string(255), notnull: true, unique: true }

JobeetJob:
  actAs: { Timestampable: ~ }
  columns:
    category_id:  { type: integer, notnull: true }
    type:         { type: string(255) }
    company:      { type: string(255), notnull: true }
    logo:         { type: string(255) }
    url:          { type: string(255) }
    position:     { type: string(255), notnull: true }
    location:     { type: string(255), notnull: true }
    description:  { type: string(4000), notnull: true }
    how_to_apply: { type: string(4000), notnull: true }
    token:        { type: string(255), notnull: true, unique: true }
    is_public:    { type: boolean, notnull: true, default: 1 }
    is_activated: { type: boolean, notnull: true, default: 0 }
    email:        { type: string(255), notnull: true }
    expires_at:   { type: timestamp, notnull: true }
  relations:
    JobeetCategory: { local: category_id, foreign: id, foreignAlias: JobeetJobs } 

JobeetAffiliate:
  actAs: { Timestampable: ~ }
  columns:
    url:       { type: string(255), notnull: true }
    email:     { type: string(255), notnull: true, unique: true }
    token:     { type: string(255), notnull: true }
    is_active: { type: boolean, notnull: true, default: 0 }
  relations:
    JobeetCategories:
      class: JobeetCategory
      refClass: JobeetCategoryAffiliate
      local: affiliate_id
      foreign: category_id
      foreignAlias: JobeetAffiliates

JobeetCategoryAffiliate:
  columns:
    category_id:  { type: integer, primary: true }
    affiliate_id: { type: integer, primary: true }
  relations:
    JobeetCategory:  { onDelete: CASCADE, local: category_id, foreign: id }
    JobeetAffiliate: { onDelete: CASCADE, local: affiliate_id, foreign: id }

TIP 如果你决定通过编写 SQL 语句来创建表,可以通过执行 propel:build-schema 任务 生成相应的 schema.yml 配置文件。

$ php symfony propel:build-schema

上面的任务要求你已经在 databases.yml 中配置了数据库。我们将在稍后的步骤中 向你展示如何配置数据库。 如果你现在试着运行这个任务,它还不能运行因为它还不知道 此 schema 是属于哪个数据库的。

schema 是实体关系图的 YAML 格式的直接翻译。

SIDEBAR ~YAML~ 文档格式

根据 YAML 官方网站资料, YAML 的意思是 “一种面向所有编程语言的 对人友好的数据序列化标准”。

另一方面来说, YAML 是描述数据的简单语言 (strings, integers, dates, arrays, 和 hashes)。

YAML 中,结构通过缩进来表示,连续的项目通过短横线“-”表示,哈希中的键值对通过冒号“:”分割。 YAML 还有用来描述好几行相同结构数据的缩写语法,数组用 [] 括起来,哈希用 {} 括起来。

如果你还不熟悉 YAML 格式,那么该开始熟悉它了,因为 symfony 框架中广泛采用 YAML 格式 文档来编写配置文件。

在编辑 YAML 文件时你需要牢记一点: 缩进必须使用一个或多个空格,但是不能使用制表符

schema.yml 文件包含了所有的表和字段的描述信息。每个字段都通过如下信息描述:

* type: ~字段类型~ (boolean, tinyint, smallint, integer, bigint, double, float, real, decimal, char, varchar(size), longvarchar, date, time, timestamp, blobclob) * required: 设为 true 表示必须填写该字段 * ~index~: 设为 true 为该字段创建索引,或者设为 unique 在该字段上创建唯一索引。 * primaryKey: 定义表中的此字段为 ~主键(primary key)~ 。 * foreignTable, foreignReference: 定义此字段为另一个表的 ~外键(foreign key)~ 。

设置为 ~ 的字段,在 YAML 中意为 nullid, created_at, 和 updated_at), symfony 会探测最适合的配置(id是主键字段,created_atupdated_at是时间戳....)。

NOTE onDelete 属性定义了外键的 ON DELETE ~行为~。Propel 支持 CASCADE, SETNULL, 和 RESTRICT。例如,删除一条 job 记录后,jobeet_job_affiliate 中所有相关记录 也会自动通过数据库删除。如果底层的数据库引擎不支持该功能,Propel 可以做到。

* type: ~字段类型~ (boolean, integer, float, decimal, string, array, object, blob, clob, timestamp, time, date, enumgzip)。 * notnull: 设为 true 表示必须填写该字段。 * unique: 设为 true 表示在该字段上创建唯一索引。

NOTE onDelete 属性定义了外键的 ON DELETE ~行为~。Doctrine 支持 CASCADE, SETNULL, 和 RESTRICT。例如,删除一条 job 记录后,jobeet_job_affiliate 中所有相关记录 也会自动通过数据库删除。

~数据库~

symfony 框架支持所有支持 PDO 的数据库(MySQL, PostgreSQL, SQLite, Oracle, MSSQL, ...)。 ~PDO~是 PHP 自带的~数据库抽象层~。

这个教程我们就以 ~MySQL~ 为例吧:

$ mysqladmin -uroot -p create jobeet
Enter password: mYsEcret ## 口令将回显为 ********

Note 如果你愿意可以随意选择另外一个~数据库引擎~,编写适应它的代码并不困难, 因为我们会使用 ORM 为我们编写 SQL 代码。

我们需要通知 symfony 为 Jobeet 项目使用这个数据库:

$ php symfony configure:database ➥ "mysql:host=localhost;dbname=jobeet" root mYsEcret 默认的 config/~databases.yml~ 包含一个涉及 propel 的连接。 因为我们使用 Doctrine,所以我们必须先删除 config/databases.yml, 然后为 Doctrine 重新生成它。

$ rm config/databases.yml

现在简单地运行下面的命令为 Doctrine 生成一个新的数据库配置文件:

$ php symfony configure:database --name=doctrine
  ➥ --class=sfDoctrineDatabase
  ➥ "mysql:host=localhost;dbname=jobeet" root mYsEcret

任务 configure:database 有三个参数: PDO DSN、 访问数据库的用户名和口令。如果你的开发服务器没有设置口令,可省略口令。

NOTE 任务 configure:database 可将数据库配置信息保存到配置文件 config/databases.yml 里。 你也可以不运行命令,而直接手动修改该文件。

-

CAUTION 在命令行上直接输入数据库的口令虽然方便但是 不安全。 根据访问你的环境的人员编辑 config/databases.yml 文件改变口令是更好的选择。 当然为了保证口令的安全,必须严格设置这个配置文件的访问权限。

ORM

要感谢在 schema.yml 文件中对数据库的描述,我们可以用 ##ORM## 内置的任务来生成 ~SQL~ 语句创建数据库中的表:

为了生成 SQL 语句必须先从你的 schema 文件创建模型。

$ php symfony doctrine:build-model

既然模型已经准备好了,你现在就能生成并插入 SQL 语句了。

$ php symfony propel:build-sql

任务 propel:build-sqldata/sql/目录生成 SQL 语句,并针对我们配置的数据库 引擎做了适当优化:

[sql] # snippet from data/sql/lib.model.schema.sql CREATE TABLE jobeet_category ( id INTEGER NOT NULL AUTO_INCREMENT, name VARCHAR(255) NOT NULL, PRIMARY KEY (id), UNIQUE KEY jobeet_category_U_1 (name) )Type=InnoDB; [sql] # snippet from data/sql/schema.sql CREATE TABLE jobeet_category (id BIGINT AUTO_INCREMENT, name VARCHAR(255) NOT NULL COMMENT 'test', created_at DATETIME, updated_at DATETIME, slug VARCHAR(255), UNIQUE INDEX sluggable_idx (slug), PRIMARY KEY(id)) ENGINE = INNODB;

运行 propel:insert-sql 任务可以在数据库中创建表:

$ php symfony propel:insert-sql

因为该任务会删除原表再重新创建,此操作会提醒你确认后执行。你也可以 用 --no-confirmation 参数忽略此提醒,这在非交互的批处理文件中 运行此任务时非常有用:

$ php symfony propel:insert-sql --no-confirmation

TIP 和任何~命令行~工具一样,symfony 可以接受参数和选项。每个命令都有内置的帮助信息。 你可以通过运行 help 命令来查看:

$ php symfony help propel:insert-sql

帮助信息列出了所有可能的参数和选项,以及他们的默认值,并给出了一些很有用的示例。

ORM 还可以生成映射数据表和对象间关系的 PHP 类:

$ php symfony propel:build-model

propel:build-model 任务在 lib/model/ 目录生成用于和数据库交互的 PHP 文件。

浏览生成的文件,你可能会发现 Propel 针对每个~表~都生成了四个类,例如 jobeet_job 表:

  • JobeetJob: 此类中的对象代表 jobeet_job 表中的一条~记录~。此类默认为空。

  • BaseJobeetJob: JobeetJob 类的父类。 每当运行 propel:build-model 任务时,这个类都会被覆盖。 因此,所有的自定义操作都必须在 JobeetJob 类中编写。

  • JobeetJobPeer: 此类定义了一系列的静态方法,大多都是返回一系列 JobeetJob 对象的。 此类默认为空。

  • BaseJobeetJobPeer: JobeetJobPeer 的父类。 每当运行 propel:build-model 任务时这个类都会被覆盖。 因此,所有的自定义操作都必须在 JobeetJobPeer 类中编写。 浏览生成的文件,你可能会发现 Doctrine 针对每个~表~都生成了三个类,例如 jobeet_job 表:

  • JobeetJob: 此类中的对象代表 jobeet_job 表中的一条~记录~。此类默认为空。

  • BaseJobeetJob: JobeetJob 类的父类。 每当运行 doctrine:build-model 任务时,这个类都会被覆盖。 因此,所有的~自定义~操作都必须在 JobeetJob 类中编写。

  • JobeetJobTable: 此类定义的方法主要返回 JobeetJob 对象的方法。(The class defines methods that mostly return collections of JobeetJob objects.) 此类默认为空。

-

通过模型对象操作记录时,可以通过获取方法(get*() 类方法)和设置方法(set*() 类方法)来操作字段的值:

[php]
$job = new JobeetJob();
$job->setPosition('Web developer');
$job->save();

echo $job->getPosition();

$job->delete();

还可以直接通过链接对象来定义~外键~。

[php]
$category = new JobeetCategory();
$category->setName('Programming');

$job = new JobeetJob();
$job->setCategory($category);

propel:build-all 任务是本小节及以后都会运行的快捷方式。现在,运行此命令为 我们的 Jobeet 模型类生成表单和验证器吧。

$ php symfony propel:build-all --no-confirmation

你会在今天的最后看到验证器,表单则会在第10天的教程中详细讲解其美妙的细节。

在后面你可以发现,symfony 会为你~自动加载~ PHP 类文件,这意味着你不再需要在 代码中写 require 语句了。这是 symfony 自动为开发者做处理的杂事儿中的一点。 但这也有不好的一面,每新增一个类都得清空一下 symfony 的~缓存~。 由于 propel:build-model 任务创建了许多新类,让我们先清理缓存:

 $ php symfony cache:clear

TIP symfony ~任务~ 由命名空间和任务名称组成。 只要不和其他命令冲突,都可以尽可能的简写。 因此,下面这些命令都等价于 cache:clear

$ php symfony cache:cl
$ php symfony ca:c

因为 cache:clear 任务经常被使用,它有更简单的缩写形式:

$ php symfony cc

初始化数据

我们已经在数据库中创建了数据表,但还没有数据。作为 web 应用程序,有三种类型的数据:

  • 初始化数据: 是应用程序正常运行所必须的数据。例如。Jobeet 需要一些初始的分类。 没有的话就没有人能发布工作。我们还需要一个可以登录到后台的管理员用户。
  • 测试数据: ~测试数据~ 用于测试应用程序。作为开发者,你需要编写测试用例以确保应用 程序按照用户故事里预期的那样正常运行。最好的方式就是编写用例进行自动化 测试。每次运行测试用例的时候,都需要清空数据库并加载一些测试数据。
  • 用户数据: 用户数据是应用程序正常运行时用户创建的数据。

每一次 symfony 创建数据库表结构时,所有的数据都会丢失。要向数据库中填充初始化数据, 我们可以编写 PHP 脚本或者通过 mysql 程序运行一些 SQL 语句。由于要经常用到, symfony 里面有更好的方式:在 data/fixtures/ 目录下创建 YAML 文件,然后用 propel:data-load 任务加载到数据库中:

首先创建如下的 ~fixtures~ 文件:

[yml] # data/fixtures/010_categories.yml JobeetCategory: design: { name: Design } programming: { name: Programming } manager: { name: Manager } administrator: { name: Administrator }

# data/fixtures/020_jobs.yml
JobeetJob:
  job_sensio_labs:
    category_id:  programming
    type:         full-time
    company:      Sensio Labs
    logo:         sensio-labs.gif
    url:          http://www.sensiolabs.com/
    position:     Web Developer
    location:     Paris, France
    description:  |
      You've already developed websites with symfony and you want to
      work with Open-Source technologies. You have a minimum of 3
      years experience in web development with PHP or Java and you
      wish to participate to development of Web 2.0 sites using the
      best frameworks available.
    how_to_apply: |
      Send your resume to fabien.potencier [at] sensio.com
    is_public:    true
    is_activated: true
    token:        job_sensio_labs
    email:        job@example.com
    expires_at:   2010-10-10

  job_extreme_sensio:
    category_id:  design
    type:         part-time
    company:      Extreme Sensio
    logo:         extreme-sensio.gif
    url:          http://www.extreme-sensio.com/
    position:     Web Designer
    location:     Paris, France
    description:  |
      Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
      eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
      enim ad minim veniam, quis nostrud exercitation ullamco laboris
      nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
      in reprehenderit in.

      Voluptate velit esse cillum dolore eu fugiat nulla pariatur.
      Excepteur sint occaecat cupidatat non proident, sunt in culpa
      qui officia deserunt mollit anim id est laborum.
    how_to_apply: |
      Send your resume to fabien.potencier [at] sensio.com
    is_public:    true
    is_activated: true
    token:        job_extreme_sensio
    email:        job@example.com
    expires_at:   2010-10-10

[yml] # data/fixtures/categories.yml JobeetCategory: design: name: Design programming: name: Programming manager: name: Manager administrator: name: Administrator

# data/fixtures/jobs.yml
JobeetJob:
  job_sensio_labs:
    JobeetCategory: programming
    type:         full-time
    company:      Sensio Labs
    logo:         sensio-labs.gif
    url:          http://www.sensiolabs.com/
    position:     Web Developer
    location:     Paris, France
    description:  |
      You've already developed websites with symfony and you want to work
      with Open-Source technologies. You have a minimum of 3 years
      experience in web development with PHP or Java and you wish to
      participate to development of Web 2.0 sites using the best
      frameworks available.
    how_to_apply: |
      Send your resume to fabien.potencier [at] sensio.com
    is_public:    true
    is_activated: true
    token:        job_sensio_labs
    email:        job@example.com
    expires_at:   '2010-10-10'

  job_extreme_sensio:
    JobeetCategory:  design
    type:         part-time
    company:      Extreme Sensio
    logo:         extreme-sensio.gif
    url:          http://www.extreme-sensio.com/
    position:     Web Designer
    location:     Paris, France
    description:  |
      Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
      eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
      enim ad minim veniam, quis nostrud exercitation ullamco laboris
      nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
      in reprehenderit in.

      Voluptate velit esse cillum dolore eu fugiat nulla pariatur.
      Excepteur sint occaecat cupidatat non proident, sunt in culpa
      qui officia deserunt mollit anim id est laborum.
    how_to_apply: |
      Send your resume to fabien.potencier [at] sensio.com
    is_public:    true
    is_activated: true
    token:        job_extreme_sensio
    email:        job@example.com
    expires_at:   '2010-10-10'

NOTE 这个 fixture 文件用到了两个图片文件。你可以下载 (http://www.symfony-project.org/get/jobeet/sensio-labs.gif, http://www.symfony-project.org/get/jobeet/extreme-sensio.gif) 然后将他们放到 uploads/jobs/ 目录下。

fixtures 文件用 YAML 格式编写,定义了模型对象并用唯一的名字打上了标签 (例如:我们定义了 job_sensio_labsjob_extreme_sensio 两个标签)。 这些标签让我们不必定义~主键~(这通常是自增的也没办法预先设定的)就可以 链接关联对象。例如 job_sensio_labs 工作的分类是 programming, 它是 'Programming' 分类的标签。

TIP YAML 文件中,当一个字符串要跨越多行时(例如 job fixture 文件中的 description 字段), 你可以使用管道符 (|) 指明字符串要跨越多行。

虽然一个 fixture 文件可以包含一个或多个模型中的对象,我们还是决定在 Jobeet 的 fixture 中为每个模型创建一个文件。

>TIP >注意文件名中的前缀数字,这是控制数据加载先后顺序的简单方法。 >在本项目后期,如果我们需要插入一些新的 fixture,可以简单地使用已存在的数字之间的预留数字前缀。 >因为其间有一些预留的数字前缀可用(例如 011 到 019)。 >NOTE >Propel 要求在 fixture 文件名使用数字前缀来决定文件的加载顺序。 >对 Doctrine 而言这不是必须的,因为所有的 fixture 都会被加载 >并且保存为确保外键正确设置的正确的顺序。

在 fixtures 文件中,你不必定义所有字段的值。如果没有,symfony 会自动使用在数据库 结构定义的默认值。因为 symfony 使用 ##ORM## 加载数据到数据库中,所有内置的行为 (像自动设置的 created_atupdated_at 字段的值)或者你自己加入模型类中自 定义行为都是可用的。

只需简单的运行 propel:data-load 任务就可加载初始化数据到数据库中:

$ php symfony propel:data-load

TIP propel:build-all-load 任务是 propel:build-all 任务以及紧随其后的 propel:data-load 任务的一个快捷方式。

运行 doctrine:build-all-reload 任务要确认一切的一切都已经从你的 schema 生成了。 这将会生成表单、过滤器、模型,删除数据库的所有表并重新生成他们。

$ php symfony doctrine:build-all-reload

在浏览器中查看实际效果

我们使用了很多的命令行操作,那一点也不让人激动,特别是对于 web 项目来说。 现在我们万事具备只需要创建页面来和数据库交互。

让我们看看怎样显示工作列表,怎样编辑已有工作以及怎样删除工作。 正如第一天解释的那样,symfony 项目由应用程序组成。 每个~应用程序~下面又包括若干个模块。 模块是实现应用程序功能(如 API 模块)的一套自包含的 PHP 代码, 也可以是用户针对模型可以做的一系列的操作的集合(例如工作模块)。

symfony 能够根据模型自动创建模块,提供基本的操作功能。

$ php symfony propel:generate-module --with-show
  ➥ --non-verbose-templates frontend job JobeetJob

propel:generate-module 任务为工作模型 JobeetJobfrontend 应用程序中 生成了名为 job 的模块。和大多数 symfony 命令一样,它在 apps/frontend/modules/job/ 下创建了一些文件和目录:

| 目录 | 说明 | ------------ | -------------------- | actions/ | 模块的操作 | templates/ | 模块的模板

actions/actions.class.php 文件定义了 job 模块所有可用的~动作~

| 动作名 | 说明 | -------------- | ------------------------------------------------------- | index | 显示数据表中的记录 | show | 显示指定记录的字段及其值 | new | 显示一个创建新记录的表单 | create | 创建一条新的记录 | edit | 显示修改一条已有记录的表单 | update | 根据用户提交的数据更新记录 | delete | 从数据表中删除指定的记录

现在你可以在浏览器中测试工作模块:

 http://jobeet.localhost/frontend_dev.php/job

工作模块

如果你试图编辑一个工作,因为 symfony 需要分类的文字表示,这会触发一个异常。 PHP 对象表示法可以通过定义魔术方法 __toString() 来实现。 一条分类记录的文字表示应该在 JobeetCategory 模型类文件中定义:

[php]
// lib/model/JobeetCategory.php
class JobeetCategory extends BaseJobeetCategory
{
  public function __toString()
  {
    return $this->getName();
  }
}

现在每次 symfony 需要分类的文字表示时都会调用 __toString() 方法来返回分类的 名称。因为我们差不多在所有的模型类中都要用到文字表示,因此让我们为每个类都定义 一个 __toString() 方法: 如果你试图编辑一个工作,你会注意到 Category id 的下拉菜单提供了所有的分类名称的列表。 每个选项值都是从 __toString() 方法获得的。

Doctrine 通过猜测一个列名的描述(如:title, name, subject 等) 试着提供一个基本的 ~__toString()~ 方法。 如果想要自定义,你必须像下面那样添加自己的 __toString() 方法。 JobeetCategory 模块能够通过 jobeet_category 表使用的 name 列猜测 __toString() 方法。

[php]

// lib/model/JobeetJob.php // lib/model/doctrine/JobeetJob.class.php class JobeetJob extends BaseJobeetJob { public function __toString() { return sprintf('%s at %s (%s)', $this->getPosition(), ➥ $this->getCompany(), $this->getLocation()); } }

// lib/model/JobeetAffiliate.php // lib/model/doctrine/JobeetAffiliate.class.php class JobeetAffiliate extends BaseJobeetAffiliate { public function __toString() { return $this->getUrl(); } }

现在你可以创建和修改工作了。试着空缺必填字段或者填错日期看看。 对,通过对数据库结构的内省,symfony 已经创建了基本的验证规则。

validation

明天见

这就是今天的所有内容。在开始的介绍部分我就警告过你。今天,虽然我们编写了少量 PHP 代码,但是工作模型已经有一个可用的模块了,还可以进行优化和定制。 记住。没有 PHP 代码也就没有 BUG!

如果还有精力的话不妨读读针对模块和模型生成的代码,并试图了解它们是如何工作的。 如果不懂也别担心,好好睡一觉,明天我们就会讨论 web 框架最常用的原则, MVC 设计模式

和每天一样,今天编写的代码可以在 Jobeet SVN 代码仓库中获得。请使用 release_day_03 tag 检出代码:

$ svn co http://svn.jobeet.org/##ORM_LOWER##/tags/release_day_03/ jobeet/

ORM