Top / PHP関連 / Jobeetチュートリアル第3日目(Doctrine版)

Jobeetチュートリアル第3日目(Doctrine版)

作成中...。

 
 
 
 
 
 

http://www.symfony-project.org/jobeet/1_2/Doctrine/ja/03

 

この節では、symfonyの2番目のORMであるDoctrineにおける内容を記載しております。
ちなみに次期バージョンである1.3のデフォルトのORMは、Doctrineになるそうです。

時間の目安

第3日目のチュートリアルが完了するまでの目安

3時間~10時間

DB周りの設定やデータモデルの検討をします。
第3日目は、第1日目と同様に結構重めのタスクになります。
さらっと流すと3時間、じっくり試すと10時間かかると思われます。

Doctorineとは

symfonyのデフォルトのORMは、Propelというフレームワークです。
Propelは、Apache Torqueにインスパイヤ(影響)されているフレームワークです。

 

ただし、Apache Torgueは、Javaの世界では、それほど採用例が多くないフレームワークなため、情報量が少ないです。

 

一方、Doctorineは、Javaの世界におけるORMフレームワークであるHibarnateにインスパイヤ(影響)されているフレームワークです。
また、symfony1.3(次期バージョン)のデフォルトのORMになるそうです。
Hibarnateは、Javaの世界ではよく使われているフレームワークであるため、いろいろと解説記事があり、情報量が多いです。
ただし、HibarnateのPHP版であるDoctorineの情報量は少ないため、Hibarnateの記事からDoctrineに変換して考えなければなりません。

 

いずれにせよ、どちらも一長一短があり、甲乙つけがたいのですが、自分はDoctorineを選択します。

Doctorineを使えるようにする

symfonyのデフォルトのORMは、Propelなので、Doctorineを使うためには若干変更を加えなければなりません。

 

まず、最初にデフォルトのORMをPropelからDoctorineを使用するように変更します。
ProjectConfigurationクラスのsetup()メソッドを以下のように書き換えます。

 

config/ProjectConfiguration.class.php

public function setup()
{
  $this->enablePlugins(array('sfDoctrinePlugin'));
  $this->disablePlugins(array('sfPropelPlugin'));
}
 

また、Propelでしか使用しない以下のファイルを削除します。

  • config/propel.ini
  • config/schema.yml
 

また、web/sfDoctrinePluginのサブディレクトリも削除します。

データモデルの作成

エンティティ関係図(Entiry-Relation図)を作成します。
ER図を描くツールは、いくつかありますが、MySQLをRDBMSに選択した場合、[MySQL Workbench]を選択すべきでしょう。

 

MySQL Workbenchを使用したいがために、MySQLを選択するというのも有りだと思います。
MySQL Workbenchのインストール方法は、以下の記事を参照してください。

MySQL Workbenchで論理DBを検討する

論理DBのER図を描くためには、日本語が表示できなければなりません。
このため、[Tools]-[Options...]を選択します。
http://www.techch.com/wikiparts/Sym_34.png

 

フォントタブを選択し、フォントフェイスを[メイリオ]のような日本語フォントを選択します。
特に[メイリオ]で無くとも構わず、[MS ゴシック]でも構いません。日本語が表示できるフォントであることが必要になります。
ちなみに全部、修正する必要があります。
http://www.techch.com/wikiparts/Sym_35.png

 

論理モデルの作成

MySQL Workbenchを使用して論理DBを描いてみました。

Sym_36.png
 

作成した論理モデルです。

これにより、論理DBでお話ができるようになります。

物理モデルの作成

MySQL Workbenchを使用して、物理DBを描いてみました。

Sym_37.png
 

作成した物理モデルです。

これにより、このようにDDLスクリプトが作成できます。

 
SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';

CREATE SCHEMA IF NOT EXISTS `jobeet` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ;
USE `jobeet`;

-- -----------------------------------------------------
-- Table `jobeet`.`jobeet_category`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `jobeet`.`jobeet_category` (
  `id` INT NOT NULL AUTO_INCREMENT COMMENT 'ID' ,
  `name` VARCHAR(255) NOT NULL COMMENT 'カテゴリ名' ,
  PRIMARY KEY (`id`) ,
  UNIQUE INDEX `uk_name` USING BTREE (`name` ASC) )
ENGINE = InnoDB
COMMENT = 'カテゴリ';


-- -----------------------------------------------------
-- Table `jobeet`.`jobeet_job`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `jobeet`.`jobeet_job` (
  `id` INT NOT NULL AUTO_INCREMENT COMMENT 'ID' ,
  `category_id` INT NOT NULL COMMENT 'カテゴリID' ,
  `type` VARCHAR(255) NULL COMMENT '契約条件' ,
  `company` VARCHAR(255) NOT NULL COMMENT '企業名' ,
  `logo` BLOB NULL COMMENT 'ロゴ' ,
  `url` VARCHAR(255) NULL COMMENT 'URL' ,
  `position` VARCHAR(255) NOT NULL COMMENT '業務' ,
  `location` VARCHAR(255) NOT NULL COMMENT '就業場所' ,
  `description` LONGTEXT NOT NULL COMMENT '仕事の説明' ,
  `how_to_apply` LONGTEXT NOT NULL COMMENT '応募方法' ,
  `token` VARCHAR(255) NOT NULL COMMENT 'トークンキー' ,
  `is_public` BOOLEAN NOT NULL DEFAULT TRUE COMMENT '公開' ,
  `is_activated` BOOLEAN NOT NULL DEFAULT FALSE COMMENT '有効' ,
  `email` VARCHAR(255) NOT NULL COMMENT 'E-Mail' ,
  `expires_at` TIMESTAMP NOT NULL COMMENT '有効期限' ,
  `created_at` TIMESTAMP NOT NULL COMMENT '作成日時' ,
  `updated_at` TIMESTAMP NOT NULL COMMENT '更新日時' ,
  PRIMARY KEY (`id`) ,
  UNIQUE INDEX `uk_token` USING BTREE (`token` ASC) ,
  INDEX `fk_category` (`category_id` ASC) ,
  CONSTRAINT `fk_category`
    FOREIGN KEY (`category_id` )
    REFERENCES `jobeet`.`jobeet_category` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB
COMMENT = '求人';


-- -----------------------------------------------------
-- Table `jobeet`.`jobeet_affiliate`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `jobeet`.`jobeet_affiliate` (
  `id` INT NOT NULL AUTO_INCREMENT COMMENT 'ID' ,
  `url` VARCHAR(255) NOT NULL COMMENT 'URL' ,
  `email` VARCHAR(255) NOT NULL COMMENT 'E-Mail' ,
  `token` VARCHAR(255) NOT NULL COMMENT 'トークンキー' ,
  `is_active` BOOLEAN NOT NULL DEFAULT FALSE COMMENT '有効' ,
  `created_at` TIMESTAMP NOT NULL COMMENT '作成日時' ,
  PRIMARY KEY (`id`) ,
  UNIQUE INDEX `uk_affiliate` USING BTREE (`email` ASC) )
ENGINE = InnoDB
COMMENT = 'アフィリエイト';


-- -----------------------------------------------------
-- Table `jobeet`.`jobeet_category_affiliate`
-- -----------------------------------------------------
CREATE  TABLE IF NOT EXISTS `jobeet`.`jobeet_category_affiliate` (
  `category_id` INT NOT NULL COMMENT 'カテゴリID' ,
  `affiliate_id` INT NOT NULL COMMENT 'アフィリエイトID' ,
  PRIMARY KEY (`category_id`, `affiliate_id`) ,
  INDEX `fk_category` (`category_id` ASC) ,
  INDEX `fk_affiliate` (`affiliate_id` ASC) ,
  CONSTRAINT `fk_category`
    FOREIGN KEY (`category_id` )
    REFERENCES `jobeet`.`jobeet_category` (`id` )
    ON DELETE CASCADE
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_affiliate`
    FOREIGN KEY (`affiliate_id` )
    REFERENCES `jobeet`.`jobeet_affiliate` (`id` )
    ON DELETE CASCADE
    ON UPDATE NO ACTION)
ENGINE = InnoDB
COMMENT = 'カテゴリ-アフィリエイト';



SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

schema.ymlの編集

かなり道を外れてしまったので、もとに戻ります。
チュートリアルでは、データモデルをYAML形式で作成した後、PHPモデル、DBのDDLを作るという順序で
DB周りの設定をしようとしています。
チュートリアルの[config/doctrine/schema.yml]は、以下のようになっています。
(configディレクトリに[doctrine]という名のサブディレクトリを掘って、そこに[schema.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: { onDelete: CASCADE, 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 }
 

schema.yml から DDLスクリプトを作成するタスクを実行するには、以下のようなコマンドを打ちます。

symfony doctrine:build-sql
 

出来上がったDDLスクリプトは、以下のファイルになります。
実際には、改行が含まれず、非常に見にくいDDLが作成されますが、整形しました。

CREATE TABLE jobeet_affiliate
(
  id BIGINT AUTO_INCREMENT,
  url VARCHAR(255) NOT NULL,
  email VARCHAR(255) NOT NULL UNIQUE,
  token VARCHAR(255) NOT NULL,
  is_active TINYINT(1) DEFAULT '0' NOT NULL,
  created_at DATETIME,
  updated_at DATETIME,
  PRIMARY KEY(id)
) ENGINE = INNODB;

CREATE TABLE jobeet_category
(
  id BIGINT AUTO_INCREMENT,
  name VARCHAR(255) NOT NULL UNIQUE,
  created_at DATETIME,
  updated_at DATETIME,
  PRIMARY KEY(id)
) ENGINE = INNODB;

CREATE TABLE jobeet_category_affiliate
(
  category_id BIGINT,
  affiliate_id BIGINT,
  PRIMARY KEY(category_id, affiliate_id)
) ENGINE = INNODB;

CREATE TABLE jobeet_job
(
  id BIGINT AUTO_INCREMENT,
  category_id BIGINT NOT NULL,
  type VARCHAR(255),
  company VARCHAR(255) NOT NULL,
  logo VARCHAR(255),
  url VARCHAR(255),
  position VARCHAR(255) NOT NULL,
  location VARCHAR(255) NOT NULL,
  description TEXT NOT NULL,
  how_to_apply TEXT NOT NULL,
  token VARCHAR(255) NOT NULL UNIQUE,
  is_public TINYINT(1) DEFAULT '1' NOT NULL,
  is_activated TINYINT(1) DEFAULT '0' NOT NULL,
  email VARCHAR(255) NOT NULL,
  expires_at DATETIME NOT NULL,
  created_at DATETIME,
  updated_at DATETIME,
  INDEX category_id_idx (category_id),
  PRIMARY KEY(id)
) ENGINE = INNODB;

ALTER TABLE jobeet_category_affiliate
  ADD FOREIGN KEY (category_id)
  REFERENCES jobeet_category(id)
  ON DELETE CASCADE;

ALTER TABLE jobeet_category_affiliate
  ADD FOREIGN KEY (affiliate_id)
  REFERENCES jobeet_affiliate(id)
  ON DELETE CASCADE;

ALTER TABLE jobeet_job
  ADD FOREIGN KEY (category_id)
  REFERENCES jobeet_category(id) 
  ON DELETE CASCADE;

PropelとDoctrineの相違

Propelが作ったDDLとDoctrineが作ったDDLについて幾つか相違があったので、列挙しておきます。

  1. Doctrineの場合、テーブルをDROPするDDLは作成されない。(別の処理でDROPするようです。)
  2. IDのカラムの型が、Propelの場合、INT(4バイト)、Doctrineの場合、BIGINT(8バイト)になっている。
  3. Doctrineの場合、actAs: { Timestampable: ~ }とすると、schema.ymlに未定義のcreate_at,update_atのカラムが自動的に作成される。(データ型はDATETIME型)
  4. 上記について、書かないと定義されない。(jobeet_category_affiliateが例)
  5. Doctorineの場合、ユニークキーの名前は、DBにお任せ。
    (お任せされたDBが付けた名前は、jobeet_category_affiliate_ibfk_1と、jobeet_category_affiliate_ibfk_2、およびjobeet_job_ibfk_1になっていました。)
  6. リレーション定義は、ALTER TABLEによってコーディングされる。
  7. シングルクォーテーションで括られていないので、スペースなどは含められない。(特に障害になるとは思えませんが、念のため)
  8. Propelが作るDDLは、人間が見ても判りやすいように改行やコメントなどが入るが、Doctrineが作るDDLは、非常に見づらい。
    (バージョンがあがれば、きっと見やすくなるように改良されるでしょう。さして重要なことでは無いのですが)

symfonyが要求するスキーマ定義

MySQL Workbenchで作成した[jobeet.sql]と[data/sql/schema.sql]を比較して判ったこと

 
  • idカラムは、NOT NULL制約を付け、AUTO_INCREMENTを付ける
  • created_atカラムと、updated_atカラムは、NOT NULL制約がつかず、DATETIME型で作られる
  • BOOL型(BOOLEAN型)のカラムは、TINYINT型で代替される
  • コメントは消える
  • create_atだけというテーブルは作れない。
  • IDのカラムの型は、BIGINTにする。
 

doctrine:insert-sqlタスクを実行すると、[data/sql/schema.sql]で定義されたDDLでスキーマーが上書き、保存されてしまいます。
もちろん、スキーマの情報が消えるという警告は出ます。
とはいえ、doctrine:insert-sqlは実行する可能性が高く、[data/sql/schema.sql]と[jobeet.sql]が不一致である点はいただけません。

 

このため、RAD開発を維持するため、外部定義のDDLは、symfonyが想定するDDL定義を同一にする必要があります。

 

ということで、物理モデルを修正してみました。

#ref(): Usage:([pagename/]attached-file-name[,parameters, ... ][,title])

作成した物理モデルです。

新しいDDLスクリプトです。

これにより、ほとんど一緒になります。

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL';

CREATE SCHEMA IF NOT EXISTS `jobeet` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ;
USE `jobeet`;

-- -----------------------------------------------------
-- Table `jobeet`.`jobeet_category`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `jobeet`.`jobeet_category` ;

CREATE  TABLE IF NOT EXISTS `jobeet`.`jobeet_category` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID' ,
  `name` VARCHAR(255) NOT NULL COMMENT 'カテゴリ名' ,
  `create_at` DATETIME NULL ,
  `update_at` DATETIME NULL ,
  PRIMARY KEY (`id`) ,
  UNIQUE INDEX `uk_name` USING BTREE (`name` ASC) )
ENGINE = InnoDB
COMMENT = 'カテゴリ';


-- -----------------------------------------------------
-- Table `jobeet`.`jobeet_job`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `jobeet`.`jobeet_job` ;

CREATE  TABLE IF NOT EXISTS `jobeet`.`jobeet_job` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID' ,
  `category_id` BIGINT NOT NULL COMMENT 'カテゴリID' ,
  `type` VARCHAR(255) NULL COMMENT '契約条件' ,
  `company` VARCHAR(255) NOT NULL COMMENT '企業名' ,
  `logo` VARCHAR(255) NULL COMMENT 'ロゴ' ,
  `url` VARCHAR(255) NULL COMMENT 'URL' ,
  `position` VARCHAR(255) NOT NULL COMMENT '業務' ,
  `location` VARCHAR(255) NOT NULL COMMENT '就業場所' ,
  `description` TEXT NOT NULL COMMENT '仕事の説明' ,
  `how_to_apply` TEXT NOT NULL COMMENT '応募方法' ,
  `token` VARCHAR(255) NOT NULL COMMENT 'トークンキー' ,
  `is_public` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '公開' ,
  `is_activated` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '有効' ,
  `email` VARCHAR(255) NOT NULL COMMENT 'E-Mail' ,
  `expires_at` DATETIME NOT NULL COMMENT '有効期限' ,
  `created_at` DATETIME NULL COMMENT '作成日時' ,
  `updated_at` DATETIME NULL COMMENT '更新日時' ,
  PRIMARY KEY (`id`) ,
  UNIQUE INDEX `uk_token` USING BTREE (`token` ASC) ,
  INDEX `fk_category` (`category_id` ASC) ,
  CONSTRAINT `fk_category`
    FOREIGN KEY (`category_id` )
    REFERENCES `jobeet`.`jobeet_category` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB
COMMENT = '求人';


-- -----------------------------------------------------
-- Table `jobeet`.`jobeet_affiliate`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `jobeet`.`jobeet_affiliate` ;

CREATE  TABLE IF NOT EXISTS `jobeet`.`jobeet_affiliate` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID' ,
  `url` VARCHAR(255) NOT NULL COMMENT 'URL' ,
  `email` VARCHAR(255) NOT NULL COMMENT 'E-Mail' ,
  `token` VARCHAR(255) NOT NULL COMMENT 'トークンキー' ,
  `is_active` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '有効' ,
  `created_at` DATETIME NULL COMMENT '作成日時' ,
  `update_at` DATETIME NULL ,
  PRIMARY KEY (`id`) ,
  UNIQUE INDEX `email` USING BTREE (`email` ASC) )
ENGINE = InnoDB
COMMENT = 'アフィリエイト';


-- -----------------------------------------------------
-- Table `jobeet`.`jobeet_category_affiliate`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `jobeet`.`jobeet_category_affiliate` ;

CREATE  TABLE IF NOT EXISTS `jobeet`.`jobeet_category_affiliate` (
  `category_id` BIGINT NOT NULL COMMENT 'カテゴリID' ,
  `affiliate_id` BIGINT NOT NULL COMMENT 'アフィリエイトID' ,
  PRIMARY KEY (`category_id`, `affiliate_id`) ,
  INDEX `fk_category` (`category_id` ASC) ,
  INDEX `fk_affiliate` (`affiliate_id` ASC) ,
  CONSTRAINT `fk_category`
    FOREIGN KEY (`category_id` )
    REFERENCES `jobeet`.`jobeet_category` (`id` )
    ON DELETE CASCADE
    ON UPDATE NO ACTION,
  CONSTRAINT `fk_affiliate`
    FOREIGN KEY (`affiliate_id` )
    REFERENCES `jobeet`.`jobeet_affiliate` (`id` )
    ON DELETE CASCADE
    ON UPDATE NO ACTION)
ENGINE = InnoDB
COMMENT = 'カテゴリ-アフィリエイト';



SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;
 

データベース接続

データベースに、ユーザーとスキーマを作成します。
チュートリアルでは、rootでログインしていますが、アプリケーション用のユーザーを作成するのが一般的です。
MySQL Administratorで、[jobeet]ユーザーを作ってみました。
ユーザー名とパスワードは同一です。(公開していないので、パスワードを公開しても大丈夫です。) http://www.techch.com/wikiparts/Sym_39.png

 

symfonyにJobeetプロジェクト用にこのデータベースを使うことを伝えます。
(この例では改行を入れていますが、実際には1行で入力します。)

symfony configure:database --name=doctrine 
--class=sfDoctrineDatabase "mysql:host=localhost;dbname=jobeet" jobeet jobeet

これにより、config/databases.yml ファイルが以下のように更新されます。

all:
  doctrine:
    class: sfDoctrineDatabase
    param:
      dsn: 'mysql:host=localhost;dbname=jobeet'
      username: jobeet
      password: jobeet

データベースへの接続情報が設定されます。

スキーマの作成

[data/sql/schema.sql]を使って、MySQLの[jobeet]スキーマに、DDLを流してスキーマを構成します。
以下のコマンドを投入します。

 symfony doctrine:insert-sql

このようにスキーマが構成されました。
http://www.techch.com/wikiparts/Sym_40.png

 

まだまだ続く...。

最新の10件
2010-07-09 2010-07-08 2010-07-07 2010-06-29 2010-06-28
人気の20件
Counter: 843, today: 1, yesterday: 0

添付ファイル: filejobeet2.sql 24件 [詳細] filejobeet2.mwb 24件 [詳細] filedata.sql.schema.sql 34件 [詳細]