symfony book 日本語ドキュメント

symfonyのモデルについて

概要

symfony は Propel プロジェクトに基づくオブジェクト/リレーショナルレイヤーを実装しています。データベースにあるデータにアクセスし修正することを、データベースのオブジェクトトランスレーションを通すことで簡単に行えます。この章においてこのオブジェクトデータモデルである、Propel によるデータアクセスと修正方法について説明します。また、symfony における Propel との連動方法について例示します。

なぜ抽象レイヤーを利用するのか

データベースは相関的なものです。PHP5 と symfony はオブジェクト指向です。オブジェクト指向においてデータベースにアクセスするには、オブジェクトロジックを相関ロジックに変換するためのインターフェースが必要になります。これはオブジェクトリレーショナルマッピング ( ORM ) といわれています。

これは symfony の MVC アーキテクチャーの Model レイヤーになります。オブジェクト自身の中でデータにアクセスし、ビジネスルールを守るオブジェクトからできています。

オブジェクト/リレーショナル抽象レイヤーの利点の1つは利用しているデータベース特有の構文を使わなくてすむことです。モデルオブジェクトが現在のデータベースに最適化された SQL を呼び出し自動的に解釈してくれます。

これはプロジェクトの途中において他のベータベースシステムに切り替えることが容易であることを意味します。あるアプリケーションのためのプロトタイプを書いていたとしましょう。しかし、クライアントはどのデータベースシステムが適切かを決めかねています。たとえば SQLite でアプリケーション開発を開始したとします。そしてクライアントが決定したときに、MySQL , PostgreSQL はたまた Oracle に切り替えるとします。そのときは設定ファイルを一行書き換えるだけで動作します。

抽象レイヤーはデータロジックをカプセル化します。アプリケーションの残りの部分はどんな SQL が必要かについて知らなくてもよく、またデータベースに接続している SQL を探すのは容易になります。データベースプログラミングに精通している開発者はどこを見えばよいかということも一目瞭然になります。

レコードの代わりにオブジェクトを、テーブルの代わりにクラスを利用することについては別の利点もあります。それは、テーブルに対して新しい接続方法を追加することです。たとえば、'Client' という 'Firstname' , 'LastName' の2つのフィールドを持つテーブルがあるとします。ここであなたは単に 'Name' としてデータを必要とするでしょう。オブジェクト指向の世界において、Client クラスに新しいアクセス関数を追加することと同じぐらい簡単なことなのです。

 
public function getName()
{
  return $this->getFirstName.' '.$this->getLastName();
}
 

全ての繰り返しデータは関数で接続することができ、データ自身のビジネスロジックはそのようなオブジェクト内で保持することができます。'ShoppingCart' クラスという ( オブジェクトである ) アイテムを保持しているクラスを想像してください。清算するためにショッピングカートの総額を取得するために、次の関数を追加することができます。

 
public function getTotal()
{
  $total = 0;
  foreach ($this->getItems() as $item)
  {
    $total += $item->getPrice();
  }
  return $total;
}
 

同じような作業を行う SQL 文を書くためにどれぐらい時間がかかるでしょうか?

Propel は現在において PHP5 に適したオブジェクト/リレーショナル抽象レイヤーの 1 つです。それを書き直す代わりに、symfony はフレームワークに Propel を何の問題もなく集積しています。

データモデル

目的

PHPが使用することができるデータオブジェクトモデルを作成するために、データベースがオブジェクトデータモデルにもっているどんなリレーショナルモデルでも解釈できるようにする必要があります。 schema.yml というスキーマファイルを利用して、テーブルのリレーションやカラムの定義を行います。

schema.ymlファイルは myproject/config/ ディレクトリに配置されなければなりません。ファイルは YAML 構文を使います。しかし、フォーマットの知識がなくても読むことができるぐらい簡単なものであり、 ( 実際 5 分もあれば習得できます )

さて、'blog' というすでに 2 つのデータを含んでいるテーブル 'blog_article', 'blog_comment' があるとしましょう。そしてそれらの構造は次のようになっています。

blog_article blog_comment
id id
title article_id
content author
created_at content
created_at

関連する schema.yml は次のようになるはずです。

propel:
  blog_article:
    _attributes: { phpName: Article }
    id:
    title:       varchar(255)
    content:     longvarchar
    created_at:
  blog_comment:
    _attributes: { phpName: Comment }
    id:
    article_id:
    author:      varchar(255)
    content:     longvarchar
    created_at:

データベースそのもの ( blog ) の名前は schema.yml のなかには書かれていないことに注意してください。その代わりにデータベースは接続名 ( この例では propel )で記述されます。正確な接続設定はアプリケーションが実行されている環境に依存するからです。たとえば、アプリケーションを開発環境で動かしているとすると、開発環境用のデータベースで、製品用のデータベースと同じスキーマだけど異なる接続設定を持っているものにアクセスするでしょう。これらの設定は databases.yml ( 下記参照 ) で指定します。

基本的なスキーマ構文

schema.yml において、最初のキーは接続を表しています。複数のテーブルを含むことができ、各接続は 1 組のカラムを持っています。 YAML 構文に従って、キーはカラムで終わり、構造は (1 つかそれ以上のタブではなく、スペースの ) インデントを通して表さなくてはなりません。複数の schema.yml ファイルをあなたのプロジェクトに持たせることが可能です。スキーマファイルを分けたときに外部テーブルを参照することができる外部キーについては下記注意書きをみてください。

テーブルには特別な属性である、生成されるクラス名を表す phpName属性 を持たせることができます。もしこれを書かなければ、テーブル名は今までのルールに従って命名されます。今までのルールとはテーブル名のアンダースコアを取り除き内側の単語の最初の文字を大文字にした名前です。 ( たとえばデフォルトのテーブルの phpNameblogArticle, blogComment になります )。カラムも phpName 属性をもっており、これは name のバージョンを大文字にした ( ID, TITLE, CONTENT など ) phpName です。そして、ほとんどの場合は上書きする必要はありません。

カラムは boolean, integer, float, date, varchar(size), longvarchrなどの値をとることができる type 属性をもっています。文字列タイプのカラムは最大文字数を規定する size 属性を持つことができます。256 文字以上の内容であれば、logchar タイプを使う必要があります。longchar タイプは文字数の規定はないが、( MySQL が規定している ) 65kb を超えることはできません。datetimestamp タイプは Unix 日付の限定があるので、1970-01-01 より以前は指定できません。より古い日付を利用するのであれば ( 例えば誕生日など ) 、' Unix 以前' の日付フォーマットを bu_datebu_timestamp などと一緒に使う必要があります。

もちろん、あなた自身のプライマリキー、外部キー、デフォルト値やインデックスなどを指定したいなら、スキーマの構文を拡張することができます。 ( 下記参照 )

スキーマの規約

id で命名された空のカラムはプライマリキーとみなされます。

_id で終わる空のカラムは外部キーとみなされ、自動的に最初の部分の名前に一致するテーブルに関連付けられます。

created_at または updated_at で命名されたカラムは自動的に timestamp 型にセットされます。

すべてのこれらのカラムにとって、型を指定する必要はありません。なぜなら、symfony はそれらの名前から推測するからです。 こうすることによって schema.xml はより簡単に記述することができるようになっています。

オブジェクトデータモデルファイル

生成について

これからオブジェクトデータモデルについて説明します。これは、生成されることによって用意される PHP クラスのことです。プロジェクトディレクトリにおいて、コマンドを入力してください。

$ symfony propel-build-model

基底データアクセスクラスは、myproject/lib/model/om/ ディレクトリに自動的に作成されていることでしょう。

BaseArticle.php
BaseArticlePeer.php
BaseComment.php
BaseCommentPeer.php

さらに、実際に利用するクラスは myproject/lib/model に作成されているでしょう。

Article.php
ArticlePeer.php
Comment.php
CommentPeer.php

2つのテーブルを定義し、8つのファイルで終わりです。どうなってるのでしょう?

基底モデルと現行モデル

なぜ、model/om/ の中と model/ の中に、2 つのオブジェクトモデルのバージョンがあるのでしょうか?

おそらく、モデルオブジェクト ( ->getName() 関数という FirstNameLastName の両方を出力するように考えられているオブジェクト ) にカスタム関数と属性を追加する必要があるでしょう。しかし、あなたのプロジェクトを開発していくと、テーブルやカラムを追加するでしょう。schema.yml にいつ修正を加えても、新たに symfony propel-build-model コマンドをオブジェクトモデルクラスを生成するために呼び出しさえすればよいのです。もしカスタマイズした関数生成されたクラスに書いていたなら、生成後に消されてしまいます。

model/om/ ディレクトリにある 基底 となるクラスは Propel によって生成されたクラスです。あなたはそれらの基底クラスを決して修正してはいけないんです。なぜなら、新しいモデルの構築によってこれらのファイルは完全に削除されるでしょう。しかし、model/ ディレクトリにある通常のオブジェクトクラスは前述した基底クラスを継承しています。そのため、ここにカスタム関数を追加することができるのです。

たとえば、新しく model/Article.php ファイルを作成する場合です。

 
class Article extends BaseArticle
{
}
 

これは BaseArticle クラスの全関数を継承しています。しかし、モデルにおける修正は規定クラスには影響しません。 この構造はカスタマイズをしやすい進化的な構造の両面を持っています。

この仕組みにデータベースの最終的な関連モデルを知らなくてもコーディングを始めることができるのです。

Peer クラス

ArticleComment クラスによって関連するテーブルのレコードの属性にアクセスすることができます。 つまり、Article オブジェクトの関数を呼び出すだけで、記事のタイトルをしることができるのです。

 
$article = new Article();
...
$title = $article->getTitle();
 

しかしレコードの中のデータよりも多くのものがデータベースにあります。つまり、実際はレコードを見つける必要もあるのです。 Peer クラスの目的はそこにあります。これらのクラスはレコードを見つけるための関数を提供し、オブジェクトかまたは、' Peer でない'クラスに関連したオブジェクトのリストを返します。

 
$articles = ArticlePeer::retrieveByPks(array(123, 124, 125));
// $articles is an array of objects of class Article
 

データモデルという点でいうと、 Peer オブジェクトというものはありません。そういうわけで、 Peer クラスの関数を -> ではなく ( 通常のオブジェクト関数の呼び出しではなく ) :: で(クラスの関数を静的に)呼び出します。

データベースアクセス

Propel において、データにオブジェクトを通してアクセスします。もしリレーションモデルに慣れていて、取得したり変更したりする SQL になれているなら、Propel 関数はおそらく複雑にみえることでしょう。しかしいったんデータアクセスのためのオブジェクトの順応性に触れてしまうと ( サンプルが Propel のドキュメントにあります ) 、大変気に入ることでしょう。

フィールドアクセス

Propel はschema.yml に定義された各テーブルに対して 1 つのクラスを提供します。これらのクラスは標準クリエイター、アクセッサー、ミューテーターを実装しています。つまり、テーブルのカラムへアクセスし 作成取得セット するための関数のを持っています。

 
$article = new Article();
$article->setTitle('My first article');
$article->setContent('This is my very first article.\n Hope you enjoy it!');
 
$title   = $article->getTitle();
$content = $article->getContent();
 

テーブルphpName 属性はクラス名のために使用されているということに注意してください。そして、アクセッサーは各言葉の最初の文字を大文字にし、下線を取り除いたカラム名 CamelCase を利用します。

外部を参照しているテーブルにレコードを作製するためには、オブジェクトに外部キーを渡すだけです。

 
$comment = new Comment();
$comment->setAuthor('Steve');
$comment->setContent('Gee, dude, you rock: best article ever !');
$comment->setArticle($article);
 

最終行は自動的に $article オブジェクトの Id 属性の値をComment オブジェクトの ArticleId 属性にセットしています。もちろん、手動で次のようにもできます。

 
$comment->setArticleId($article->getId());
 

new でコンストラクタを呼んで、 Article テーブルに実際の記録を作っているのではなく、新しいオブジェクトを作っていることに注意してください。データベースにデータを保存するためには、 save() でオブジェクトを保存する必要があります。

 
$article->save();
 

Propel のカスケーディング原則によって $comment オブジェクトも保存する必要はありません。つまり、あなたが保存したものにレコードはリンクされているので、自動的に保存されます。

ヒント: 複数のフィールドを一度にセットするために、Propelオブジェクトの ->fromArray() 関数を使うことができます。

$article->fromArray(array(
   'title'   =>   'My first article',
   'content' =>   'This is my very first article.\n Hope you enjoy it!',
));

関連付けられたレコードへのアクセス

新しい記事についての全コメントをチェックしてみましょう。

 
$comments = $article->getComments();
 

Propel は Comment テーブルにある article_id カラムを見て、記事は複数のコメントを持っている ( 1 対多リレーション ) と理解します。 関連するコメントを配列で取得するために、コードを生成する際に、 getComments() 関数は Article オブジェクトの中に生成されます。 get 関数において使われている複数形に注意してください。$comments 変数は Comment クラスのオブジェクト配列を含んでいますので、最初の1つを表示するには

 
print_r($comments[0]);
 

とします。

もし、記事のコメントを読むと、インターネット上に公開するのをどうだろうと思うかもしれないですよね。そして、もし記事を読んだ人の皮肉を気にしないのなら、 delete() 関数で記事を簡単に削除できます。

 
foreach($comments as $comment)
{
  $comment->delete();
}
 

多対多リレーションの場合でさえも次のようにシンプルになります。

 
echo $comment->getArticle()->getTitle();
 

getArticle() 関数は Article クラスのオブジェクトを返します。そして Article クラスは getTitle() アクセッサから取得します。 これはあなたがテーブルをジョインさせるより簡単なことであり、( $comment->getArticleId() 呼び出しで始まる ) 数行のコーディングで可能です。

Propel ドキュメントにより多くの構文について説明があります。

レコードアクセス

もし特定のレコードの主キーがわかっているのなら、関連するオブジェクトを取得するためのテーブルの Peer クラスの retrieveByPk() 関数を使うことができます。たとえば、

 
$article = ArticlePeer::retrieveByPk(7);
 

schema.ymlArticle テーブルの主キーとなる id フィールドを定義しています、それで、この記述は実際 id が 7 の記事を返します。主キーを使用することで、たった1つのレコードが返されるということがわかります。それで、 $article 変数は Article クラスのオブジェクトを含んでいます。

簡単なクエリのために、 Propel はSQLを使わずデータベースを抽象化してくれます。実際、 Criteria オブジェクトを提供してくれ、これは強力であり利用が簡単です。

たとえば、全記事を取得するには、次のようにタイプします

 
$c = new Criteria();
$articles = ArticlePeer::doSelect($c);
 

クラス関数であるdoSelect() はパラメータとして受け取ったセクションフィルターを適用しています。この例においては、 $c オブジェクトは規則を持たされていないので、 doSelect()Article テーブルの全レコードを返すでしょう。( これは 'SELECT *'のSQL文と同じですね )。$articles 変数は全ての Article クラスのオブジェクトの配列を含んでいます。それらに順々にアクセスするためには、ただ、foreach 構文を使うだけです。

 
foreach ($articles as $article)
{
  echo $article->getTitle();
}
 

しかし、全レコードを取得したり、キーによって1つのレコードを取得するより多くの要望があります。Criteria オブジェクトの力はルールを追加することで 'WHERE' であったり 'ORDER BY' 文をSQLの中に追加できることです。

Steveが投稿した全コメントを投稿日順で取得するには

 
$c = new Criteria();
$c->add(CommentPeer::AUTHOR, 'Steve');
$c->addAscendingOrderByColumn(CommentPeer::CREATED_AT);
$comments = CommentPeer::doSelect($c);
 

もしくは、彼のコメントで狼狽してしまうので、Steve以外による全投稿を取得したいときは

 
$c = new Criteria();
$c->add(CommentPeer::AUTHOR, 'Steve', Criteria::NOT_EQUAL);
$c->addAscendingOrderByColumn(CommentPeer::DATE);
$comments = CommentPeer::doSelect($c);
 

ここで使用されているクラス定数名に注意してください。というのも、オブジェクトデータモデル説明におけるカラムの phpNames があります。 ::doSelect() 関数はあなたのデータベースの種類にもっともベストなSQLクエリを生成し、実行し、オブジェクトのリストを返すでしょう。それらはレコードをセットするよりも優れています。

注意: comment.AUTHOR という、SQLクエリを出力する方法を用いる代わりに CommentPeer::AUTHOR を使うのでしょう? author フィールドの名前が contributor にデータベースにおいて変更されたとしましょう。もし、comment.AUTHOR を使用していると、あなたのコードにある Propel オブジェクトの全ての呼び出し場所を修正しなければなりません。CommentPeer::AUTHOR を使用していれば、schema.yml のカラム名を変えるだけで、複数の修正をせずに phpNameAUTHORを保持することができます。

Criteria オブジェクトとその関数の詳細については Propel ドキュメント を参照してください。

created_atupdated_at カラム

普段は、テーブルが created_at カラムを定義されているとき、生成されたときの日付のタイムスタンプを保存します。 同じことが updated_at にも当てはまり、それ自身のデータが更新される度にその時刻で更新したりします。

良い知らせですが、symfony はこれらの名前を認識し、あなたがしたい上記動作を行ってくれます。もう手動で created_atupdated_at カラムを設定する必要がありません。 それらは自動的に更新されます。

 
$comment = new Comment();
$comment->setAuthor('Steve');
$comment->save();
// show the creation date
echo $comment->getCreatedAt();
 

さらに、これらのカラムへのゲッターは引数として日付フォーマットを受け付けます。

 
echo $comment->getCreatedAt('Y-m-d');
 

データベースアクセス設定

Database access configuration

データモデルはデータベースの使用とは関係がありません、しかし、間違いなくデータベースを使うことになるでしょう。プロジェクトのデータベースにリクエストが結び付けるために symfony が必要な最小限の情報は名前、アクセスコード、データベースの種類です。myproject/config/ ディレクトリにある databases.yml に設定されるべきです。

prod:
  propel:
    param:
      host:               mydataserver
      username:           myusername
      password:           xxxxxxxxxx

all:
  propel:
    class:                sfPropelDatabase
    param:
      phptype:            mysql
      host:               localhost
      database:           blog
      username:           root
      password:
      compat_assoc_lower: true
      # datasource:       propel
      # encoding:         utf8
      # persistent:       true

データベース接続は環境に依存します。なので、製品版 ( prod )、開発版 ( dev )、テスト版 ( test) または、あなたが定義したそれ以外の環境のそれぞれの環境に合わせた異なる設定を定義できます。全てのヘッダは全環境のための設定を定義しています。この設定はアプリケーションに特化したファイル、たとえば myproject/apps/myapp/config/databases.yml ファイルに、異なる値を設定することでアプリケーションごとに上書きできます。

注意: host パラメータは hostspec として定義することができます。

各環境にたいして、多数の接続を schema.yml ファイルにリンクさせることで定義できます。コネクションとスキーマは datasource パラメータによって結び付けられています。このパラメータは schema.yml にある最初のキーを参照しています。もし、datasource パラメータを規定していなければ、接続設定で与えられたラベルの値を利用します。

phptype で指定できる値は Propel によってサポートされているデータベースシステムのいずれかです。

host, database, usernamepassword設定はデータベースに接続するときに必要となります。

注意: persistent パラメータは永続接続設定を定義することができます。

上記のような例では、全ての環境の設定は簡単明瞭な構文で書くことができます。

all:
  propel:
    class:          sfPropelDatabase
    param:
      dsn:          mysql://root:@localhost/blog

注意: SQLite データベースを使用するなら、host パラメータにはデータベースファイルへのパスをセットしてください。もし、myproject/data/blog.db にブログのデータベースがあるのであれば、databases.yml は次のようになります。

all:
  propel:
    class:          sfPropelDatabase
    param:
      dsn:          sqlite://./../data/blog.db

拡張されたスキーマ構文

データベース属性

データベースは属性を持っています。それらは _attributes: キーの中に設定されます。

あなたのスキーマを生成処理されるまえに有効にしたいかもしれない、その場合に、noXsd 属性を無効にする

propel:
  _attributes:   { noXsd: false }

主要素は defaultIdMethod 属性をサポートしているので、もし、データベースのID生成関数を使うとき何もしません。 -- たとえば、MySQL の autoincrement や、PostgreSQL のシーケンスなどです。 もうひとつの有効な値は none です。

propel:
  _attributes:   { defaultIdMethod: none }

各スキーマは package 属性を持っています。パッケージは スキーマに基づき生成されたクラスのロケーションを定義します。デフォルトでは全てのデータベースは lib.model の一部を宣言されています。 - というわけで、生成されたファイルは lib/model/ ディレクトリにあるのです。あなたは異なるパッケージを指定することでサブディレクトリにクラスファイルを置くようにすることができます。 もし、複数の schema.yml ファイルを使いたいなら、グループが同じパッケージのクラスに関連づけられているか確認してください。もしプラグインを使うなら、インクルードされたスキーマは plugins.pluginName.lib.model という形式でパッケージ名を提供しないといけません。

propel:
  _attributes:   { package: lib.model.myPackage }

テーブル属性

データベースのように、テーブルは特定の属性を持つことができます。スキーマ定義において、これらの属性は _attributes: キー 以下で定義されます。

もう既に何度もみている phpName テーブル属性は、生成されるクラスをテーブルにマッピングする名前です。

propel:
  blog_article:
    _attributes: { phpName: Article }

ローカリゼーションされた内容を含むテーブル (たとえば、複数の種類のコンテンツがあったり、関連付けられたテーブルであったり、国際化のためにであったり )も 2 つの属性をとることができます。 ( 国際化の章で詳しく説明しています )

propel:
  blog_article:
    _attributes: { isI18N: true, i18nTable: db_group_i18n }

カラムの詳細

基本的な構文は2つの方法があります。symfony は ( 空の値を与えることによって ) カラム名から性質を推測し、型キーワードから1つを定義することができます。

propel:
  blog_article:
    id:                 # let symfony do the work
    title: varchar(50)  # specify the type yourself

しかし、1つのカラムにより多くの情報を定義することができます。そして、この場合に、連想配列でカラム設定を定義する必要があります。

propel:
  blog_article:
    id:       { type: integer, required: true, primaryKey: true, autoincrement: true }
    name:     { type: varchar , size: 50, default: foobar, index: true }
    group_id: { type: integer, foreignTable: db_group, foreignReference: id, onDelete: cascade }

カラムパラメータは次のようなものです。

カラムマジック

"スキーマ規約" の章で symfony は解釈できるカラム名があり、それらから推測することができると書きました。 これはスキーマでカラムのために値が定義されていないときに発生します。規約は名前を次のように属性に翻訳します。

# 'id' カラムをプライマリキーとする
id:         { type: integer, required: true, primaryKey: true, autoIncrement: true }
# 'XXX_id' カラムを外部キーとする
foobar_id:  { type: integer, foreignTable: db_foobar, foreignReference: id }
# 'created_at', 'updated_at', 'created_on', 'updated_on' カラムをタイムスタンプとする
created_at: { type: timestamp }

( _id で終わる全てのカラムに対して作用する )外部キーマジック のために、symfony は phpName が カラム名の最初の部分を探します。そして、該当するものを見つけると、foreignTable としてこのテーブル名を解釈します。

さらに、プライマリキーがテーブルに定義されていないと、symfony は自動的に id カラムをオートインクリメントの数値を格納するカラムとして追加します。

テーブルマジック (I18n)

symfony はコンテンツのローカリゼーションを関連付けられたテーブルにてサポートしています。ローカリゼーションの影響を受けている内容があれば、2 つの分離されたテーブルに保存します。1 つはそのままのカラムで、もう一方はローカライズされたカラムです。

schema.yml において、foobar_i18n というテーブル名を付けると、全てが次のようであると暗に意味します。

propel:
  db_group:
    id:          -
    created_at:  -

  db_group_i18n:
    name:        varchar(50)

symfony は自動的にカラムを追加し、ローカライズされた内容に自動的になるためにテーブル属性を追加します。 前述のスキーマは次のように実装することを意味します。

propel:
  db_group:
    _attributes: { isI18N: true, i18nTable: db_group_i18n }
    id:          -
    created_at:  -

  db_group_i18n:
    id:          { type: integer, required: true, primaryKey: true, foreignTable: db_group, foreignReference: id, onDelete: cascade }
    culture:     { isCulture: true, type: varchar, size: 7, required: true, primaryKey: true }
    name:        varchar(50)

外部キー

外部キーを定義する 3 つの方法があります。

カラム属性と比べると、最後の構文は多重参照外部キーにとって興味深く外部キーに名前を与えます

    _foreign_keys:
      my_foreign_key:
        foreign_table: db_user
        onDelete:      cascade
        references:
          - { local: user_id, foreign: id }
          - { local: post_id, foreign: id }

注意: 個々の schema.yml ファイルで定義されているテーブル間の外部キーは上記のようにはっきりと定義されなくてはならなりません。なぜなら symfony によって自動的に検出されないからです。また、それぞれのデータベースが同じパッケージに属していることを宣言すること ( 上記 属性 の章をみてください )と、propel.packageObjectModel = truepropel.ini において記述されていることを保証しなければなりません ( これらの設定は現在は symfony プロジェクトにおいてデフォルトです )。

インデックス

index カラム属性に代わるものとして、手動で 1 つか複数のインデックスをテーブル内に _indexes キーで追加することができます。ユニークなインデックスを定義したいなら、次のように _uniques ヘッダーを使わなければなりません。

propel:
  blog_article:
    id:
    title:            varchar(50)
    created_at:
    _indexes:
      my_index:       [title, user_id]
    _uniques:
      my_other_index: [created_at]

明白な外部キー構文として、1 つ以上のカラムで貼られたインデックスが有用です。

schema.ymlを超えている The schema.xml

事実、 schema.yml フォーマットは symfony 内部のものです。 propel- コマンドを呼ぶときには、symfony はこのファイルを generated-schema.xml という Propel が読み込めるモデルのフォーマット形式のファイルに翻訳します。

schema.xml ファイルは YAML が実装しているような同じ情報を含んでいます。 前述のArticle/Comment スキーマを XML フォーマットで定義すると次のようになります。

 
<?xml version="1.0" encoding="UTF-8"?>
 <database name="propel" defaultIdMethod="native" noxsd="true">
    <table name="blog_article" phpName="Article">
      <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
      <column name="title" type="varchar" size="255" />
      <column name="content" type="longvarchar" />
      <column name="created_at" type="timestamp" />
    </table>
    <table name="blog_comment" phpName="Comment">
      <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
      <column name="article_id" type="integer" />
      <foreign-key foreignTable="blog_article">
        <reference local="article_id" foreign="id"/>
      </foreign-key>
      <column name="author" type="varchar" size="255" />
      <column name="content" type="longvarchar" />
      <column name="created_at" type="timestamp" />
    </table>
 </database>
 

schema.xml フォーマットの記述は Propel プロジェクトのウェブサイトの ドキュメントgetting started の章にあります。

YAML フォーマットはスキーマをシンプルに読んだり、書いたりできるように設計されていました。しかし、それと引き換えに schema.yml では記述できないような複雑なことが可能となっています。 一方で、XML フォーマットによって完全なスキーマの記述が行えます。そして、特定のデータベースベンダーを含むことができたり、テーブルの継承を行えたりします。

symfony は XML フォーマットで書かれたスキーマを理解できます。なので、 YAML 構文で設定するには複雑になったり、 XML スキーマを既に持っていたり、Propel の schema.xml に慣れ親しんでいるなら、 YAML 構文に切り替える必要はありません。あなたの schema.xml をプロジェクトの config/ ディレクトリにおいて、モデルを構築し、作業してください。

モデルを2度も作成しない

存在する schema に基づいて SQL データベース構造を構築

もし、 schema.yml を書くことによってアプリケーションをスタートさせるのなら、データベースのテーブルを作成する SQL 文を書くことを、2 度はしたくないでしょう。symfony は YAML データモデルから直接データベースを作成するクエリを生成することができます。そのクエリを生成するために、root プロジェクトディレクトリに移動し、次のようにタイプします

$ symfony propel-build-sql

lib.model.schema.sqlmyproject/data/sql/ ディレクトリに作成されるでしょう。生成された SQL コードは databases.yml ファイルの phptype パラメータで指定したデータベースシステムに最適化されたものになることに注意してください。また、propel.ini で使いたいコネクションを指定することができます。

また、製品環境のpropel接続のための設定をmyappというアプリケーションに対して定義しているdatabases.ymlというファイルがあるなら

テーブルを作成するために直接 lib.model.schema.sql を使うことができます。たとえば、MySQLならば

$ mysqladmin -u root -p create blog
$ mysql -u root -p blog < data/sql/lib.model.schema.sql

このコマンドは別の環境に再構築したり、RDBMSを変更するときに役立つでしょう。もし接続設定が propel.ini できちんと行われていれば、自動的に処理する symfony propel-insert-sql コマンドを使うことができます。

Tip: プロジェクトの開発フェーズにおいて、おそらく何度も修正するでしょう。毎回 schema.yml を修正し、 propel-build-model を呼び出すことでモデルを再構築しなければなりません。そのため、propel-build-sqlpropel-insert-sql でデータベース構造を再構築します。幸いなことに、symfony はこれら 3 つのタスクを次のコマンドで実行できます。

 $ symfony propel-build-all

既存するデータベースから YAML データモデルを生成

symfony は Creole データベースアクセスレイヤーを 既存データベースからschema.yml ファイルを生成するために利用します。このリバースエンジニアリングしたりオブジェクトモデル上で作業する前にデータベース上で作業するのが好きな場合などに便利です。

このようにするために、プロジェクトの propel.ini が現在のデータベースを指し示し、全ての接続設定を含まなければなりません。

以上の用意ができれば propel-build-schema コマンドを呼ぶことができます。

$ symfony propel-build-schema

そして、データベース構造から構築された新しい schema.ymlmyproject/config/ ディレクトリに書き出されます。この構造に基づいたモデルを生成することができます。

スキーマ生成コマンドは強力で、スキーマに多くのデータベースの情報を追加することができます。 YAML フォーマットはこの種のベンダー情報を操作しないので、XML スキーマを生成する必要があります。 といっても、build-schema タスクに引数を加えるだけです。

$ symfony propel-build-schema xml

schema.yml ファイルを生成する代わりに、完全に Propel に対応している、全てのベンダー情報を含む schema.xml を生成することができます。 また、生成された XML スキーマは可読性が低いことに気づくでしょう。

複数のデータベース

実際のところ、複数のデータベース接続設定が 1 つのプロジェクトで設定することができ、複数のデータベースにデータを通知することができます。こうするために、複数の schema.yml ファイルを書くことができます (特別な名前の接頭辞を持つ schema.yml で、認識することができる名前です)。

たとえば、blogimage repository の 2 つの部分に接続を分けたい場合に、プロジェクトの config/ ディレクトリに次のように 2 つのファイルを生成します。

// in blog_schema.yml
blog:
  table1:
    column1: ...
    column2: ...

//in image_schema.yml
imagerep:
  table1:
    column1: ...
    column2: ...

データレイヤーのリファクタリング

symfony プロジェクトを開発していくと、アクションにおいてドメインロジック ( ビジネスルール ) コードから書き始めることがしばしばあります。プロジェクトが複雑になっていくと、アクションは多くの PHP と SQL コードを含みます。そして、可読性が低くなってしまいます。

このときに、データに関係する全てのロジックは Model レイヤーに動かされるべきです。アクションにおいてひとつ以上の場所で同じリクエストを繰り返す必要があるときはいつでも、関連するコードを Model レイヤーに移動させることを考えなければなりません。そうすることでアクションをより小さく可読性のあるものにしておくことができます。

たとえば、ウェブログにおいて ( リクエストパラメータを通過した ) タグに関連する 10 の最も人気のある記事を検索するために必要なコードを想像してください。 このコードは Action ではなく Model レイヤーに書かれるべきです。実際、テンプレートでリストを表示する必要があるなら、アクションは単純に次のようになります。

 
public function executeShowPopularArticlesForTag()
{
  $tag = TagPeer::retrieveByName($this->getRequestParameter('tag'));
  $this->articles = $tag->getPopularArticles(10);
}
 

アクションはリクエストパラメータから Tag クラスのオブジェクトを生成しています。そして、データベースに問い合わせる必要がある全てのコードは このクラスの ->getPopularArticles() 関数の部分になります。アクションにこの関数のコードを含めてしまうと可読性はさらに低くなります。

コードをより適した場所に移してしまうことはリファクタリングのテクニックの1つです。何度もおこなうことで、あなたのコードは他の開発者によって理解されやすいメンテナンスしやすいコードになるでしょう。データレイヤーのリファクタリングをいつ行うべきかという良い経験則はアクションの PHP コードが 10 行を超えたときです。

symfony における Propel

これまでに説明してきた全ては symfony にとって特別なことではなく、実際に、オブジェクト/リレーショナル抽象レイヤーとして Propel を無理に使わなくてもいいんです。しかし、symfony は Propel とはとても相性よく動作します。なぜなら、

そして、symfony がそうであるように、Propel もデータベースを使用するかどうかは関係がありません。


original: http://www.symfony-project.com/content/book/page/model.html

[←インデックスに戻る][↑TOPへ戻る]