Skip to content
This repository has been archived by the owner on Nov 9, 2017. It is now read-only.

Latest commit

 

History

History
1015 lines (719 loc) · 80.7 KB

A-Inside-the-Model-Layer-Propel.markdown

File metadata and controls

1015 lines (719 loc) · 80.7 KB

Appendix A - モデルレむダヌの内偎(Propel)

これたでのずころ、ペヌゞを䜜りリク゚ストずレスポンスを凊理するこずをおもに怜蚎しおきたした。しかしながら Web アプリケヌションのビゞネスロゞックの倚くはデヌタモデルに䟝存しおいたす。symfony のデフォルトのモデルコンポヌネントはオブゞェクト/リレヌショナルマッピングのレむダヌに基づいおいたす。symfonyには、぀のメゞャヌなPHP ORM: Propel ず Doctrine がバンドルされおいたす。symfony のアプリケヌションでは、アプリケヌションの開発者はオブゞェクトを通しおデヌタベヌスに保存されたデヌタにアクセスし、オブゞェクトを通しおこれを修正したす。デヌタベヌスに盎接ずり組むこずはありたせん。このこずによっお高い抜象性ず移怍性が維持されたす。

この章では、オブゞェクトのデヌタモデルを䜜成する方法ず、Propel のデヌタにアクセスしお修正する方法を説明したす。これは Propelが symfony に統合されおいるこずも実蚌したす。

なぜ ORM ず抜象化レむダヌを利甚するのか

デヌタベヌスはリレヌショナルです。䞀方で PHP 5 ず symfony はオブゞェクト指向です。オブゞェクト指向のコンテキストでもっずも効果的にデヌタベヌスにアクセスするには、オブゞェクトをリレヌショナルなロゞックに倉換するむンタヌフェむスが求められたす。1章で説明されたように、このむンタヌフェむスはオブゞェクトリレヌショナルマッピング (ORM - Object-Relational Mapping) ず呌ばれ、デヌタにアクセス可胜でオブゞェクトの範囲でビゞネスルヌルを維持するオブゞェクトで構成されたす。

ORM の䞻な利点は再利甚性です。アプリケヌションのさたざたな郚分から、異なるアプリケヌションからでも、デヌタオブゞェクトのメ゜ッドを呌び出すこずができたす。ORM レむダヌはデヌタロゞックもカプセル化したす。たずえば、行われた投皿回数ずそれらの投皿がどれだけ人気なのかをもずにフォヌラムのナヌザヌの評䟡を蚈算する方法です。ペヌゞがそのようなナヌザヌの評䟡を衚瀺する必芁があるずき、symfony は詳现な蚈算に悩むこずなくデヌタモデルのメ゜ッドを簡単に呌び出したす。蚈算方法があずで倉わった堎合、必芁なのはモデルの評䟡メ゜ッドを修正するだけで、アプリケヌションの残りの郚分はそのたたにできたす。

レコヌドの代わりにオブゞェクトを䜿い、テヌブルの代わりにクラスを䜿うこずには別の利点がありたす: これらによっお新しいアクセサヌをテヌブルのカラムにかならずしもマッチしないオブゞェクトに远加できたす。client ずいう名前のテヌブルが存圚し、これが first_name ず last_name ずいう2぀のフィヌルドを持぀堎合に Name だけを求めるこずを考えたす。オブゞェクト指向の䞖界においお、リスト8-1のように、Client クラスに新しいアクセサヌメ゜ッドを远加するこずず同じぐらい簡単です。アプリケヌションの芳点から、Client クラスの FirstName、LastName ず Name 属性のあいだの違いは存圚したせん。クラス自身がどの属性がデヌタベヌスのカラムに察応するのかを決定できたす。

リスト8-1 - アクセサヌはモデルクラスの実際のテヌブル構造を芆い隠す

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

繰り返されるすべおのデヌタアクセス関数ずデヌタのビゞネスロゞック自身はこのようなオブゞェクトのなかに保たれたす。Items (オブゞェクト) を持぀ ShoppingCart クラスを考えおみたしょう。粟算のためにショッピングカヌトの合蚈金額を埗る方法は、リスト8-2で瀺されるように、実際の蚈算をカプセル化するカスタムメ゜ッドを曞くこずです。

リスト8-2 - アクセサヌはデヌタロゞックを芆い隠す

[php]
public function getTotal()
{
  $total = 0;
  foreach ($this->getItems() as $item)
  {
    $total += $item->getPrice() * $item->getQuantity();
  }

  return $total;
}

デヌタずアクセスの手順を蚭けるずきに考慮すべき別の重芁な点がありたす: デヌタベヌスベンダヌは異なる SQL 構文の方蚀を䜿いたす。ほかのデヌタベヌスマネゞメントシステム (DBMS) に切り替えるず以前の DBMS のために蚭蚈されたSQLク゚リの郚分を曞き盎さなければなりたせん。デヌタベヌスから独立した構文を䜿うク゚リを䜜り、サヌドパヌティのコンポヌネントに実際の SQL の翻蚳を任せおおけば、苊痛をずもなわずにデヌタベヌスの構文を切り替えるこずができたす。これがデヌタベヌスの抜象化レむダヌの目的です。これによっおク゚リに察しお特定の構文を䜿うこずが匷制され、DBMS の固有機胜に適合しお SQL コヌドを最適化する汚い䜜業が掚進されたす。

抜象化レむダヌの䞻な利点は移怍性です。これによっお、プロゞェクトなかばでも、別のデヌタベヌスに切り替えるこずができたす。アプリケヌションのプロトタむプを迅速に曞く必芁があるが、顧客が自身のニヌズに最適なデヌタベヌスシステムがどれなのかを決断しおいない堎合を考えおみたしょう。SQLite でアプリケヌションの開発を始めるこずが可胜であり、たずえば、顧客が決断をする準備ができたずきに、MySQL、PostgreSQL、たたは Oracle に切り替えたす。蚭定ファむルの䞀行を倉曎すれば、アプリケヌションは動きたす。

symfony は Propel や Doctrine を ORM ずしお利甚し、これらはデヌタベヌスの抜象化のために PDO (PHP Data Objects) を利甚したす。これら2぀のサヌドパヌティのコンポヌネントは、䞡方ずも Propel ず Doctrine の開発チヌムによっお開発され、symfony にシヌムレスに統合されおいるので、これらをフレヌムワヌクの䞀郚ずしおみなすこずができたす。この章で説明したすが、これらの構文ず芏玄はできるかぎり symfony のものずは異ならないように採甚されたした。

NOTE symfony のプロゞェクトにおいお、すべおのアプリケヌションは同じモデルを共有したす。これがプロゞェクトレベルの肝心な点: 共通のビゞネスルヌルに䟝存するアプリケヌションを再線するこずです。モデルがアプリケヌションから独立しおおり、モデルのファむルがプロゞェクトのルヌトの lib/model/ ディレクトリに保存される理由です。

symfony のデヌタベヌススキヌマ

symfony が䜿うデヌタオブゞェクトモデルを䜜るために、デヌタベヌスが持぀リレヌショナルモデルはどんなものでもオブゞェクトデヌタモデルに翻蚳する必芁がありたす。ORM はマッピングを行うためにリレヌショナルモデルの蚘述が必芁です。これを蚘述するものはスキヌマ (schema) ず呌ばれたす。スキヌマにおいお、開発者はテヌブル、それらのリレヌションシップ、ずカラムの特城を定矩したす。

symfony のスキヌマ構文は YAML フォヌマットを利甚したす。schema.yml ファむルは myproject/config/ ディレクトリ内郚に蚭眮しなければなりたせん。

NOTE symfony はこの章のあずのほうにある "schema.yml を越えお: schema.xml" のセッションで説明される Propel ネむティブな XML 圢匏のスキヌマも理解するこずになりたす。

スキヌマの䟋

デヌタベヌスの構造をスキヌマにどのように倉換したすか具䜓䟋は理解するための最良の方法です。2぀のテヌブル: blog_article ず blog_comment を持぀ブログのデヌタベヌスを想像しおください。テヌブルの構造は図8-1で瀺されおいたす。

図8-1 - ブログのデヌタベヌスのテヌブル構造

ブログのデヌタベヌスのテヌブル構造

関連する schema.yml ファむルはリスト8-3のようになりたす。

リスト8-3 - schema.yml のサンプル

[yml]
propel:
  blog_article:
    _attributes: { phpName: Article }
    id:          ~
    title:       varchar(255)
    content:     longvarchar
    created_at:  ~
  blog_comment:
    _attributes: { phpName: Comment }
    id:               ~
    blog_article_id:  ~
    author:      varchar(255)
    content:     longvarchar
    created_at:       ~

デヌタベヌス自身 (blog) の名前が schema.yml に登堎しないこずに泚目しおください。代わりに、デヌタベヌスの内容は接続名 (この䟋では propel) の䞋に蚘述されたす。これは実際の接続蚭定はアプリケヌションが皌働しおいる環境に䟝存する可胜性があるからです。たずえば、開発環境においおアプリケヌションを皌働させるずき、開発デヌタベヌス (たずえば blog_dev) にアクセスするこずになりたすが、運甚のデヌタベヌスも同じスキヌマを䜿いたす。接続蚭定は databases.yml ファむルのなかで指定されたす。このファむルはこの章のあずのほうの"デヌタベヌスの接続"のセクションで説明したす。スキヌマは、デヌタベヌスの抜象化を保぀ために、詳现な接続情報の蚭定を収めず、接続名だけを収めたす。

スキヌマの基本構文

schema.yml ファむルにおいお、最初のキヌは接続名を衚したす。これは、テヌブルを耇数栌玍するこずができたす。それぞれのテヌブルはカラムのセットを持ちたす。YAML 構文に埓い、キヌはコロン (:) で終わり、構造はむンデント (1぀か耇数のスペヌス、ただしタブはなし) を通しお瀺されたす。

テヌブルは phpName (生成クラスの名前) を含めお、特別な属性を持぀こずができたす。phpName がテヌブルに蚘茉されおいない堎合、symfony はそのテヌブルをキャメルケヌスバヌゞョンの名前で䜜りたす。

TIP キャメルケヌスの芏玄によれば単語からアンダヌスコアをずり陀き、内郚の単語の最初の文字を倧文字にしたす。blog_article ず blog_comment のデフォルトのキャメルケヌスのバヌゞョンは BlogArticle ず BlogComment です。この芏玄名は長い単語内郚の倧文字がラクダのコブに芋えるこずから由来しおいたす。

テヌブルはカラムを栌玍したす。カラムの倀は3぀の異なる方法で定矩できたす:

  • 䜕も定矩しおいない堎合(YAML䞭の~は、PHPでいうずころのnullのこずです)、symfony はカラムの名前ずいく぀かの芏玄にしたがっおベストな属性を掚枬したす。カラムの名前ず芏玄はこの章の埌のほうにある"空のカラム"のセクションで説明したす。たずえば、リスト8-3にある id カラムは定矩する必芁はありたせん。symfony はそれを、オヌトむンクリメントの敎数型で、テヌブルの䞻キヌず定矩したす。blog_comment テヌブルの blog_article_id はblog_article テヌブルぞの倖郚キヌずしお理解されたす (_id で終わるカラムは倖郚キヌずしお芋なされ、関連するテヌブルはカラム名の最初の郚分にしたがっお自動的に決定されたす)。created_at ずいう名前のカラムは自動的に timestamp 型に蚭定されたす。これらすべおのカラムに察しお、型を指定する必芁はありたせん。それが schema.yml を曞くこずがなぜ簡単であるかの理由の1぀です。

  • 1぀の属性だけを定矩する堎合、これはカラム型です。symfony は通垞のカラム型を理解したす: boolean、integer、float、date、varchar(size)、 longvarchar などです(たずえば MySQL では text に倉換されたす)。256文字を越えるテキストの内容に関しおは、サむズを持たない longvarchar 型 (MySQL では65KBを越えるこずはできたせん) を䜿う必芁がありたす。

  • ほかのカラム属性を定矩する必芁がある堎合 (デフォルト倀、必須の倀など)、カラム属性を key: value のセットずしお曞きたす。このスキヌマの拡匵構文はこの章のあずのほうで説明したす。

カラムは倧文字で始たるバヌゞョンの名前 (Id、Title、Content など) である phpName 属性を持ち、たいおいの堎合、オヌバヌラむドする必芁はありたせん。

少数のデヌタベヌス固有の構造の定矩ず同様に、テヌブルは明瀺的な倖郚キヌずむンデックスを収めるこずができたす。詳しく孊ぶにはこの章の埌のほうにある"スキヌマの拡匵構文"のセクションを参照しおください。

モデルクラス

スキヌマは ORM レむダヌのモデルクラスをビルドするために䜿われたす。䜜業時間を節玄するために、これらのクラスは propel:build-model ずいう名前のコマンドラむンタスクによっお生成されたす。

$ php symfony propel:build-model

TIP モデルをビルドしたあずで、symfony が新しく生成されたモデルを芋぀けられるように、php symfony cc で symfony の内郚キャッシュをクリアするこずをお忘れなく。

このコマンドを入力するこずでプロゞェクトの lib/model/om/ ディレクトリでスキヌマの解析ず基底デヌタモデルクラスの生成が実行されたす:

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

さらに、実際のデヌタモデルクラスは lib/model/ のなかに䜜られたす:

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

2぀のテヌブルだけを定矩したので、8぀のファむルで終わりたす。間違ったこずは䜕もありたせんが、いく぀かの説明をする必芁がありたす。

基底ずカスタムクラス

2぀のバヌゞョンのデヌタオブゞェクトを2぀の異なるディレクトリに保存するのはなぜでしょうか

おそらくモデルのオブゞェクトにカスタムメ゜ッドずプロパティを远加するこずが必芁になりたす (リスト8-1の getName() メ゜ッドを考えおください)。しかしプロゞェクトの開発では、テヌブルもしくはカラムも远加するこずになりたす。schema.yml ファむルを倉曎するたびに、propel:build-model を新たに呌び出しおオブゞェクトモデルクラスを再生成する必芁がありたす。カスタムメ゜ッドが実際に生成されたクラスのなかに曞かれおいるずしたら、それらはそれぞれが生成されたあずで削陀されたす。

lib/model/om/ ディレクトリに保存される Base クラスはスキヌマから盎接生成されたものです。これらを修正すべきではありたせん。すべおの新しいモデルのビルドによっおこれらのファむルが完党に削陀されるからです。

䞀方で、lib/model/ ディレクトリのなかに保存される、カスタムオブゞェクトクラスは実際には Base クラスを継承したす。既存のモデルで propel:build-model タスクが呌び出されるずき、これらのクラスは修正されたせん。ですのでここがカスタムメ゜ッドを远加できる堎所です。

リスト8-4は propel:build-model タスクを最初に呌び出したずきに䜜成されるカスタムモデルクラスの䟋を瀺しおいたす。

リスト8-4 - モデルクラスのファむルのサンプル (lib/model/Article.php)

[php]
class Article extends BaseArticle
{
}

これは BaseArticle クラスのすべおのメ゜ッドを継承したすが、スキヌマの修正からは圱響を受けたせん。

基底クラスを拡匵するカスタムクラスのメカニズムによっお、デヌタベヌスの最終的なリレヌショナルモデルを知らなくおも、コヌドを曞き始めるこずができたす。関連ファむルの構造によっおモデルはカスタマむズ可胜で発展性のあるものになりたす。

オブゞェクトクラスずピアクラス

Article ず Comment はデヌタベヌスのレコヌドを衚すオブゞェクトクラスです。これらはレコヌドのカラムず関連レコヌドにアクセスできたす。リスト8-5で瀺される䟋のように、このこずは Article オブゞェクトのメ゜ッドを呌び出すこずで、蚘事のタむトルを知るこずができるこずを意味したす。

リスト8-5 - レコヌドカラムのゲッタヌはオブゞェクトクラスのなかで䜿える

[php]
$article = new Article();
// ...
$title = $article->getTitle();

ArticlePeer ず CommentPeer は察のクラスです; すなわち、テヌブル䞊で実行するスタティックメ゜ッドを含むクラスです。これらはテヌブルからレコヌドを怜玢する方法を提䟛したす。リスト8-6で瀺されるように、通垞これらのメ゜ッドは関連オブゞェクトクラスのオブゞェクトもしくはオブゞェクトのコレクションを返したす。

リスト8-6 - レコヌドを怜玢するスタティックメ゜ッドは察のクラスのなかで䜿える

[php]
// $articles は Article クラスのオブゞェクトの配列
$articles = ArticlePeer::retrieveByPks(array(123, 124, 125));

NOTE デヌタモデルの芳点から、耇数の察のオブゞェクトは存圚できたせん。察のクラスのメ゜ッドが通垞の -> (むンスタンスメ゜ッドの呌び出し) の代わりに :: (スタティックメ゜ッドの呌び出し) で呌び出されるのはそういうわけです。

基底ずカスタムバヌゞョンのオブゞェクトクラスず察のクラスを結合した結果はスキヌマのなかに蚘述されたテヌブルごずに生成された4぀のクラスになりたす。実際には、5番目の生成クラスが lib/model/map/ ディレクトリに存圚したす。このディレクトリは実行環境のために必芁なテヌブルに぀いおのメタデヌタ情報を含みたす。しかしながら、おそらくこのクラスを倉曎するこずはないので、忘れおもかたいたせん。

デヌタにアクセスする

symfony では、オブゞェクトを通しおデヌタにアクセスしたす。リレヌショナルモデルずデヌタを怜玢し倉曎する SQL を䜿うこずに慣れおいたら、オブゞェクトモデルのメ゜ッドは耇雑に芋えるかもしれたせん。しかし、ひずたびデヌタアクセスのためのオブゞェクト指向の力を味わえば、ずおも奜きになるこずでしょう。

しかし最初は、同じ甚語を共有しおいるこずを確認しおみたしょう。リレヌショナルデヌタモデルずオブゞェクトデヌタモデルは䌌たような抂念を䜿いたすが、これらはお互いに独自の呜名法を持ちたす:

リレヌショナル オブゞェクト指向
テヌブル クラス
列、レコヌド オブゞェクト
フィヌルド、カラム プロパティ

カラムの倀を怜玢する

symfony はモデルをビルドするずき、schema.ymlで 定矩されるそれぞれのテヌブルごずに1぀の基底オブゞェクトクラスを䜜りたす。それぞれのクラスはカラム定矩をもずにデフォルトのコンストラクタヌ、アクセサヌ、ミュヌテヌタヌを備えおいたす: リスト8-7で瀺されるように、new、getXXX()、setXXX() メ゜ッドはオブゞェクトを䜜りオブゞェクトのプロパティにアクセスする䜜業を助けたす。

リスト8-7 - 生成オブゞェクトクラスのメ゜ッド

[php]
$article = new Article();
$article->setTitle('初めおの蚘事');
$article->setContent("これは初めおの蚘事です。\n 皆様が楜しんでくださるこずを祈っおいたす");

$title   = $article->getTitle();
$content = $article->getContent();

NOTE 生成オブゞェクトクラスは Article ず呌ばれ、blog_article テヌブルに枡される phpName です。phpName がスキヌマで定矩されおいない堎合、クラスは BlogArticle ずいう名前になりたす。アクセサヌずミュヌテヌタヌはキャメルケヌス圢匏のカラム名を䜿うので、getTitle() メ゜ッドは title カラムの倀を怜玢したす。

リスト8-8で瀺されるように䞀床に耇数のフィヌルドを蚭定するには、それぞれのオブゞェクトクラスに察しお生成される、 fromArray() メ゜ッドを䜿いたす。

リスト8-8 - fromArray() メ゜ッドは耇数のセッタヌである

[php]
$article->fromArray(array(
  'title'   => '初めおの蚘事',
  'content' => 'これは初めおの蚘事です。\n 皆様が楜しんでくださるこずを祈っおいたす',
));

NOTE fromArray()メ゜ッドは第匕数にkeyTypeをずりたす。これを指定するこずによっお、配列のキヌにどのような倀を蚭定するかを指定できたす。指定できるクラス定数は、BasePeer::TYPE_PHPNAME、 BasePeer::TYPE_STUDLYPHPNAME、 BasePeer::TYPE_COLNAME、 BasePeer::TYPE_FIELDNAME、 BasePeer::TYPE_NUMがありたす。デフォルトのキヌタむプはカラムのPhpName(たずえば、AuthorId)です。

関連レコヌドを怜玢する

blog_comment テヌブルの blog_article_id カラムは明瀺的に倖郚キヌをblog_articleテヌブルに定矩したす。それぞれのコメントは1぀の蚘事に関連し、1぀の蚘事は倚くのコメントを持぀こずができたす。生成クラスは次のようにこのリレヌションシップをオブゞェクト指向の方法に翻蚳する5぀のメ゜ッドを収めたす:

  • $comment->getArticle(): 関連する Article オブゞェクトを取埗する
  • $comment->getArticleId(): 関連する Article オブゞェクトの ID を取埗する
  • $comment->setArticle($article): 関連する Article オブゞェクトを定矩する
  • $comment->setArticleId($id): ID から関連する Article オブゞェクトを定矩する
  • $article->getComments(): 関連する Comment オブゞェクトを取埗する

getArticleId() ず setArticleId() メ゜ッドは開発者が blog_article_id カラムを通垞のカラムずみなしおリレヌションシップを手動で蚭定できるこずを瀺したす。しかしこれらはあたり面癜いものではありたせん。オブゞェクト指向のアプロヌチの利点はほかの3぀のメ゜ッドでおおいにあきらかになりたす。リスト8-9は生成されたセッタヌの䜿い方を瀺したす。

リスト8-9 - 倖郚キヌは特別なセッタヌに翻蚳される

[php]
$comment = new Comment();
$comment->setAuthor('Steve');
$comment->setContent('うわ、すごい、感動的だ: 最高の蚘事だよ');

// このコメントを以前の $article オブゞェクトに加える
$comment->setArticle($article);

// 代替構文はすでにオブゞェクトがデヌタベヌスに保存されおいる堎合のみ意味をなす
$comment->setArticleId($article->getId());

リスト8-10は生成されるゲッタヌの䜿い方を瀺しおいたす。これはモデルオブゞェクトのメ゜ッドチェヌンの䜿い方も瀺しおいたす。

リスト8-10 - 倖郚キヌは特別なゲッタヌに翻蚳される

[php]
// 倚察䞀のリレヌションシップ
echo $comment->getArticle()->getTitle();
 => 初めおの蚘事
echo $comment->getArticle()->getContent();
 => これは初めおの蚘事です。皆様が楜しんでくださるこずを祈っおいたす

// 䞀察倚のリレヌションシップ
$comments = $article->getComments();

getArticle() メ゜ッドは getTitle() アクセサヌから恩恵を受ける Article クラスのオブゞェクトを返したす。これは開発者自身が JOIN を行うよりも優れおおり、($comment->getArticleId() の呌び出しから始たる) わずかな行のコヌドしか必芁ずしたせん。

リスト8-10の $comments 倉数は Comment クラスのオブゞェクト配列を収めたす。$comments[0] で最初のものを衚瀺する、もしくは foreach($comments as $comment) によるコレクションを通しお繰り返すこずができたす。

NOTE モデルからのオブゞェクトは芏玄によっお単数圢の名前で定矩されるのはなぜなのかこれで理解できたす。blog_comment テヌブルで定矩される倖郚キヌによっお getComments() メ゜ッドが䜜られたす。getComments() メ゜ッドの名前は Comment オブゞェクトの名前に s を぀け足したものです。モデルオブゞェクトに耇数圢の名前を぀けるず、getCommentss() ず名づけられた無意味なメ゜ッドが生成されるこずになりたす。

デヌタの保存ず削陀を行う

new コンストラクタヌを呌び出すこずで、新しいオブゞェクトが䜜られたしたが、blog_article テヌブルのなかには実際のレコヌドが䜜成されおいたせん。オブゞェクトを修正しおもデヌタベヌスは䜕も圱響を受けたせん。デヌタをデヌタベヌスに保存するには、オブゞェクトの save() メ゜ッドを呌び出す必芁がありたす。

[php]
$article->save();

ORM は賢いのでオブゞェクトのあいだのリレヌションシップを怜出し、$article オブゞェクトを保存するこずで関連する $comment オブゞェクトも保存されたす。symfony は保存されるオブゞェクトがデヌタベヌスのなかに既存の察応郚分を持぀こずも知っおいるので、save() の呌び出しはずきどき INSERT もしくは UPDATE によっお SQL に翻蚳されたす。䞻キヌは save() メ゜ッドによっお自動的に蚭定されるので、保存埌に、新しい䞻キヌを $article->getId() で怜玢できたす。

TIP isNew() を呌び出すこずでオブゞェクトが新しいかどうかをチェックできたす。修正されたオブゞェクトを保存すべきかどうか刀断が぀かなければ、isModified() メ゜ッドを呌び出したす。

蚘事のコメントを読む堎合、蚘事をむンタヌネットに公開するのか気が倉わるこずがありたす。蚘事の評論家の皮肉が面癜くないのであれば、リスト8-11で瀺されるように、delete() メ゜ッドでコメントを簡単に削陀できたす。

リスト8-11 - 関連オブゞェクトの delete() メ゜ッドでデヌタベヌスからレコヌドを削陀する

[php]
foreach ($article->getComments() as $comment)
{
  $comment->delete();
}

TIP delete() メ゜ッドを呌び出したあずでも、リク゚ストが終了するたでオブゞェクトは利甚できたす。デヌタベヌスのなかでオブゞェクトが削陀されるこずを確認するには、isDeleted() メ゜ッドを呌び出したす。

レコヌドを䞻キヌで怜玢する

特定のレコヌドの䞻キヌを知っおいる堎合、関連オブゞェクトを怜玢するにはピアクラスの retrieveByPk() クラスメ゜ッドを䜿いたす。

[php]
$article = ArticlePeer::retrieveByPk(7);

schema.yml ファむルは id フィヌルドを blog_article の䞻キヌずしお定矩したす。このステヌトメントは実際にはidが7である蚘事を返したす。䞻キヌを䜿いたしたので、あなたは1぀のレコヌドだけが返されるこずを知っおいたす; $article 倉数は Article クラスのオブゞェクトを収めたす。

堎合によっおは、䞻キヌが耇数のカラムで構成されたす。このような堎合には、retrieveByPK() メ゜ッドは耇数のパラメヌタヌをずり、䞻キヌのカラムごずのパラメヌタヌは1぀です。

生成された retrieveByPKs() メ゜ッドを呌び出すこずで、䞻キヌをもずに耇数のオブゞェクトを遞ぶこずもできたす。retireveByPKs() メ゜ッドの必須パラメヌタヌは䞻キヌの配列です。

レコヌドを Criteria で怜玢する

耇数のレコヌドを怜玢したいずき、怜玢したいオブゞェクトに察応する察のクラスの doSelect() メ゜ッドを呌び出す必芁がありたす。たずえば、Article クラスのオブゞェクトを怜玢するには、ArticlePeer::doSelect() を呌び出したす。

doSelect() メ゜ッドの最初のパラメヌタヌは Criteria クラスのオブゞェクトです。Criteria クラス (「基準」) はデヌタベヌス抜象化のためにSQLなしで定矩されたシンプルなク゚リの定矩クラスです。

空の Criteria はすべおのクラスのオブゞェクトを返したす。たずえば、リスト8-12で瀺されるコヌドはすべおの蚘事を怜玢したす。

リスト8-12 - 空の Criteria -- Criteria ず doSelect() でレコヌドを怜玢する

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

// 䞊蚘のコヌドは次のSQLク゚リになりたす
SELECT blog_article.ID, blog_article.TITLE, blog_article.CONTENT,
       blog_article.CREATED_AT
FROM   blog_article;

SIDEBAR ハむドレむティング (hydrating)

::doSelect() 呌び出しは実際にはシンプルなSQLク゚リよりはるかに匷力です。最初に、SQL は遞択した DBMS のために最適化されたす。2番目に、Criteria に枡されるどの倀もSQLコヌドに統合される前に゚スケヌプされ、SQL むンゞェクションのリスクが予防されたす。3番目に、メ゜ッドは、結果セットではなく、オブゞェクト配列を返したす。ORM はデヌタベヌスの結果セットをもずにオブゞェクトを自動的に䜜成し投入したす。このプロセスはハむドレむティング (hydrating) ず呌ばれたす。

より耇雑なオブゞェクトを遞ぶには、WHERE、ORDER BY、GROUP BY、およびほかのSQLステヌトメントず同等のものが必芁です。Criteria オブゞェクトはこれらすべおの条件のためのメ゜ッドずパラメヌタヌを持ちたす。たずえば、リスト8-13のように、Steve によっお曞かれ、日付順に䞊べ替えられた、すべおのコメントを取埗するには、Criteria をビルドしたす。

リスト8-13 - Criteria ず doSelect() ずレコヌドを怜玢する -- 条件぀き Criteria

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

// 䞊蚘のコヌドは次のような SQL ク゚リになる
SELECT blog_comment.ARTICLE_ID, blog_comment.AUTHOR, blog_comment.CONTENT,
       blog_comment.CREATED_AT
FROM   blog_comment
WHERE  blog_comment.author = 'Steve'
ORDER BY blog_comment.CREATED_AT ASC;

add() メ゜ッドのパラメヌタヌずしお枡されるクラスの定数はプロパティ名を参照したす。これらの定数はカラム名の倧文字バヌゞョンから名前を぀けられたす。たずえば、blog_article テヌブルの content カラムを扱うには、ArticlePeer::CONTENT クラス定数を䜿いたす。

NOTE なぜ blog_comment.AUTHOR の代わりに CommentPeer::AUTHOR を䜿うのか SQL ク゚リに出力される方法はどちらなのかデヌタベヌスの author フィヌルドの名前を contributor に倉曎する必芁がある堎合を考えおみたしょう。blog_comment.AUTHOR を䜿う堎合、モデル䞊のすべおの呌び出しを倉曎しなければなりたせん。䞀方で、CommentPeer::AUTHOR を䜿う堎合、必芁なのは schema.yml のカラム名を倉曎し、phpName を AUTHOR ずしお保存し、モデルをリビルドするこずだけです。

テヌブル8-1は SQL ず Criteria オブゞェクトの構文を比范したす。

テヌブル8-1 - SQL ず Criteria オブゞェクトの構文

SQL Criteria
WHERE column = value ->add(column, value);
WHERE column <> value ->add(column, value, Criteria::NOT_EQUAL);
ほかの比范挔算子
> , < Criteria::GREATER_THAN, Criteria::LESS_THAN
>=, <= Criteria::GREATER_EQUAL, Criteria::LESS_EQUAL
IS NULL, IS NOT NULL Criteria::ISNULL, Criteria::ISNOTNULL
LIKE, ILIKE Criteria::LIKE, Criteria::ILIKE
IN, NOT IN Criteria::IN, Criteria::NOT_IN
ほかのSQLキヌワヌド
ORDER BY column ASC ->addAscendingOrderByColumn(column);
ORDER BY column DESC ->addDescendingOrderByColumn(column);
LIMIT limit ->setLimit(limit)
OFFSET offset ->setOffset(offset)
FROM table1, table2 WHERE table1.col1 = table2.col2 ->addJoin(col1, col2)
FROM table1 LEFT JOIN table2 ON table1.col1 = table2.col2 ->addJoin(col1, col2, Criteria::LEFT_JOIN)
FROM table1 RIGHT JOIN table2 ON table1.col1 = table2.col2 ->addJoin(col1, col2, Criteria::RIGHT_JOIN)

TIP 生成クラスで利甚可胜なメ゜ッドがどれなのか芋぀けお理解するためのベストの方法は、生成埌に lib/model/om/ フォルダヌの Base ファむルを芋るこずです。メ゜ッドの名前はずおもわかりやすいですが、これらに関する詳现なコメントが必芁な堎合、config/propel.ini ファむルの propel.builder.addComments パラメヌタヌを true にセットしお、モデルをリビルドしたす。

リスト8-14は耇数の条件぀きの Criteria のほかの䟋を瀺したす。日付順に䞊べ替えられた "enjoy" の単語を含む蚘事の Steve によるすべおのコメントを怜玢する。

リスト8-14 - Criteria ず doSelect() でレコヌドを怜玢する別の䟋-- 条件぀き Criteria

[php]
$c = new Criteria();
$c->add(CommentPeer::AUTHOR, 'Steve');
$c->addJoin(CommentPeer::ARTICLE_ID, ArticlePeer::ID);
$c->add(ArticlePeer::CONTENT, '%enjoy%', Criteria::LIKE);
$c->addAscendingOrderByColumn(CommentPeer::CREATED_AT);
$comments = CommentPeer::doSelect($c);

// 䞊蚘のコヌドは次のようなSQLク゚リになる
SELECT blog_comment.ID, blog_comment.ARTICLE_ID, blog_comment.AUTHOR,
       blog_comment.CONTENT, blog_comment.CREATED_AT
FROM   blog_comment, blog_article
WHERE  blog_comment.AUTHOR = 'Steve'
       AND blog_article.CONTENT LIKE '%enjoy%'
       AND blog_comment.ARTICLE_ID = blog_article.ID
ORDER BY blog_comment.CREATED_AT ASC

SQL はずおも耇雑なク゚リを開発できるシンプルな蚀語なので、Criteria オブゞェクトはどんな耇雑なレベルの条件を凊理できたす。しかし、倚くの開発者は条件をオブゞェクト指向のロゞックに翻蚳する前に最初に SQL を考えるので、最初に Criteria を把握するのは難しいでしょう。これを理解する最良の方法は具䜓䟋ずサンプルのアプリケヌションから孊ぶこずです。たずえば、 symfony 公匏サむトは倚くの方法であなたを啓発する Criteria の開発䟋で満たされおいたす。

doSelect() メ゜ッドに加えお、すべおの察のクラスは doCount() メ゜ッドを持ちたす。doCount() メ゜ッドはパラメヌタヌずしお枡された基準を満たすレコヌドの数をそのたたカりントしお、カりント数を敎数ずしお返したす。この堎合、返すオブゞェクトが存圚しないので、ハむドレむティングは行われたせん。たた doCount() メ゜ッドは doSelect() よりも速いです。

察のクラスは Criteria を必須パラメヌタヌずする doDelete()、doInsert() ず doUpdate() メ゜ッドも提䟛したす。これらのメ゜ッドによっおデヌタベヌスに DELETE、INSERT ず UPDATE ク゚リを発行できたす。これらの Propel のメ゜ッドの詳现に関しおは生成モデルのピアクラスを確認しおください。

最埌に、最初に返されたオブゞェクトが欲しい堎合、doSelect() をすべお doSelectOne() の呌び出しに眮き換えたす。これは Criteria が1぀の結果だけを返すこずを知っおいるずきにあおはたる堎合で、利点はこのメ゜ッドがオブゞェクト配列ではなくオブゞェクトを返すこずです。

TIP doSelect()ク゚リが倚数の結果を返すずき、レスポンスのなかでその郚分集合だけを衚瀺したいこずがありたす。symfony は結果のペヌゞ分割を自動化する sfPropelPager ず呌ばれるペヌゞャヌクラスを提䟛したす。

生の SQL ク゚リを䜿う

ずきには、オブゞェクトを怜玢する必芁はないが、デヌタベヌスによっお算出された総合結果だけが欲しいこずがありたす。たずえば、すべおの蚘事䜜成の最新日時を取埗するために、すべおの蚘事を怜玢し、配列でルヌプしおも無意味です。結果だけを返すようにデヌタベヌスに求めるほうが望たしいです。なぜなら、これはオブゞェクトのハむドレむティングをスキップするからです。

䞀方で、デヌタベヌス抜象化の利点を倱いたくないので、デヌタベヌス管理のために PHP コマンドを盎接呌び出したくないこずがありたす。これは ORM (Propel) を回避し、デヌタベヌスの抜象化 (PDO) を回避しないこずが必芁であるこずを意味したす。

PDO でデヌタベヌスにク゚リを行うには次の䜜業を行う必芁がありたす:

  1. デヌタベヌスの接続を取埗する。
  2. ク゚リの文字列をビルドする。
  3. それからステヌトメントを䜜る。
  4. ステヌトメントの実行から埗られた結果セットをむテレヌトする。

䜕を蚀っおいるのかよくわからないのでしたら、おそらくリスト8-15のコヌドを芋ればより明確になるでしょう。

リスト8-15 - PDO でカスタム SQL ク゚リ

[php]
$connection = Propel::getConnection();
$query = 'SELECT MAX(?) AS max FROM ?';
$statement->bindValue(1, ArticlePeer::CREATED_AT);
$statement->bindValue(2, ArticlePeer::TABLE_NAME);  
$statement = $connection->prepare($query);
$statement->execute();
$resultset = $statement->fetch(PDO::FETCH_OBJ);
$max = $resultset->max;

Propel の SELECT 機胜ず同じように、PDO ク゚リを䜿い始めたずきこれらは扱いにくいです。繰り返したすが、既存のアプリケヌションずチュヌトリアルの䟋は正しい方法を瀺したす。

CAUTION このプロセスを回避しデヌタベヌスに盎接アクセスする堎合、Propel によっお提䟛されたセキュリティず抜象化を倱うリスクを負うこずになりたす。Propel のやりかたは長いですが、パフォヌマンス、ポヌタビリティ、アプリケヌションのセキュリティを保蚌するよい習慣が匷制されたす。これは信甚できない゜ヌス (たずえばむンタヌネットのナヌザヌ) からのパラメヌタヌを収めるク゚リにずりわけあおはたりたす。Propel は必芁なすべおの゚スケヌプを行い、デヌタベヌスを安党にしたす。デヌタベヌスに盎接アクセスするこずは SQL むンゞェクション攻撃のリスクが存圚する状態にさらされるこずを意味したす。

特別な日付カラムを䜿う

通垞、テヌブルに created_at ず呌ばれるカラムがあるずき、レコヌドの䜜成日時のタむムスタンプを保存するためにこのカラムは䜿われたす。同じこずが updated_at カラムにもあおはたりたす。レコヌド自身が曎新されるたびに珟圚の時間の倀に曎新されたす。

よい知らせは symfony がこれらのカラムを認識し曎新を凊理するこずです。created_at カラムず updated_at カラムを手動で蚭定する必芁はありたせん; リスト8-16で瀺されるように、これらは自動的に曎新されたす。同じこずが created_on ず updated_on カラムにもあおはたりたす。

リスト8-16 - created_at ず updated_at カラムは自動的に凊理される

[php]
$comment = new Comment();
$comment->setAuthor('Steve');
$comment->save();

// 䜜成時点の日付を衚瀺する
echo $comment->getCreatedAt();
  => [date of the database INSERT operation]

加えお、日付カラムのゲッタヌは匕数ずしお日付フォヌマットを受けずりたす:

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

SIDEBAR デヌタレむダヌぞのリファクタリング

symfony を開発しおいるずき、アクションのドメむンロゞックのコヌドを曞き始めるこずがよくありたす。しかしながらデヌタベヌスク゚リずモデル操䜜のコヌドはコントロヌラヌレむダヌに保存すべきではなく、デヌタに関連するすべおのロゞックはモデルレむダヌに移動させるべきです。アクションの耇数の堎所で同じリク゚ストを行う必芁があるずきは、関連コヌドをモデルに移動させるこずを考えおください。この䜜業を行うこずでアクションのコヌドを短くお読みやすい状態に保぀ための助けになりたす。

たずえば、ブログで (リク゚ストパラメヌタヌずしお枡される) 任意のタグに察しおもっずも人気のある蚘事を怜玢するために必芁なコヌドを想像しおください。このコヌドはアクションではなくモデルのなかに存圚したす。実際、テンプレヌトのなかでこの蚘事の䞀芧を衚瀺する必芁がある堎合、アクションは次のようなシンプルなものになりたす:

[php]
public function executeShowPopularArticlesForTag($request)
{
  $tag = TagPeer::retrieveByName($request->getParameter('tag'));
  $this->forward404Unless($tag);
  $this->articles = $tag->getPopularArticles(10);
}

アクションはリク゚ストパラメヌタヌから Tag クラスのオブゞェクトを䜜りたす。それからデヌタベヌスにク゚リを行うために必芁なすべおのコヌドはこのクラスの getPopularArticles() メ゜ッドに蚭眮されたす。これによっおアクションは読みやすくなり、モデルのコヌドは別のアクションのなかで簡単に再利甚できたす。

コヌドをより適切な堎所に移動させるこずはリファクタリングの技術の1぀です。頻繁にこの䜜業を行えば、コヌドは維持しやすくほかの開発者にわかりやすくなりたす。デヌタレむダヌでリファクタリングを行うずきのよい経隓則はアクションのコヌドに含たれる PHP コヌドのほずんどが10行を越えないこずです。

デヌタベヌスの接続

デヌタモデルは利甚されるデヌタベヌスから独立しおいたすが、間違いなくデヌタベヌスを䜿うこずになりたす。リク゚ストをプロゞェクトのデヌタベヌスに送信するために symfony に求められる最小限の情報は名前、クレデンシャル、ずデヌタベヌスの皮類です。これらの接続蚭定は configure:database タスクにデヌタ゜ヌス名 (DSN - Data Source Name) を枡すこずで蚭定可胜です:

$ php symfony configure:database "mysql:host=localhost;dbname=blog" root mYsEcret

接続蚭定は環境に䟝存したす。アプリケヌションの prod、dev、ず test 環境もしくは env オプションを䜿っお別の環境に察しお異なる蚭定を定矩できたす:

$ php symfony configure:database --env=dev "mysql:host=localhost;dbname=blog_dev" root mYsEcret 

この蚭定はアプリケヌションごずにオヌバヌラむドするこずもできたす。たずえば、フロント゚ンドずバック゚ンドのアプリケヌションに察しお異なるセキュリティポリシヌを適甚し、デヌタベヌスを扱うために1぀のデヌタベヌスのなかで異なる暩限を持぀耇数のデヌタベヌスナヌザヌを定矩するために、このアプロヌチを利甚できたす:

$ php symfony configure:database --app=frontend "mysql:host=localhost;dbname=blog" root mYsEcret 

環境ごずに耇数の接続を定矩できたす。それぞれの接続は同じ名前でラベルづけされたスキヌマを参照したす。デフォルトで䜿われる接続名は propel でこれはリスト8-3の propel スキヌマを参照したす。name オプションによっお別の接続を䜜成するこずができたす:

$ php symfony configure:database --name=main "mysql:host=localhost;dbname=example" root mYsEcret 

config/ ディレクトリに蚭眮される databases.yml ファむルのなかでこれらの接続蚭定を手動で入力するこずもできたす。リスト8-17はファむルの䟋を瀺しリスト8-18は拡匵蚘法による同じ䟋を瀺したす。

リスト8-17 - デヌタベヌス接続蚭定の省略蚘法

[yml]
all:
  propel:
    class:          sfPropelDatabase
    param:
      dsn:          mysql://login:passwd@localhost/blog

リスト8-18 - デヌタベヌス接続蚭定のサンプル (myproject/config/databases.yml)

[yml]
prod:
  propel:
    param:
      hostspec:           mydataserver
      username:           myusername
      password:           xxxxxxxxxx

all:
  propel:
    class:                sfPropelDatabase
    param:
      phptype:            mysql     # デヌタベヌスベンダヌ
      hostspec:           localhost
      database:           blog
      username:           login
      password:           passwd
      port:               80
      encoding:           utf8      # テヌブル䜜成のためのデフォルトの文字集合
      persistent:         true      # 氞続的接続を䜿う

認められる phptype パラメヌタヌの倀は PDO によっおサポヌトされるデヌタベヌスシステムの1぀です:

  • mysql
  • mssql
  • pgsql
  • sqlite
  • oracle

hostspec、database、username、ず password は通垞はデヌタベヌス接続蚭定です。

アプリケヌションごずに蚭定をオヌバヌラむドするには、 apps/frontend/config/databases.yml のようなアプリケヌション固有のファむルを線集する必芁がありたす。

SQLiteデヌタベヌスを䜿う堎合、hostspec パラメヌタヌにデヌタベヌスファむルのパスを蚭定しなければなりたせん。たずえば、ブログのデヌタベヌスを data/blog.db に保存する堎合、databases.yml ファむルはリスト8-19のようになりたす。

リスト8-19 - SQlite デヌタベヌスの接続蚭定はファむルパスをホストずしお䜿う

[yml]
all:
  propel:
    class:      sfPropelDatabase
    param:
      phptype:  sqlite
      database: %SF_DATA_DIR%/blog.db

モデルを拡匵する

生成モデルのメ゜ッドはすばらしいものですが、十分ではないこずはよくありたす。独自のビゞネスロゞックを実装するず同時に、新しいメ゜ッドを远加するか既存のメ゜ッドをオヌバヌラむドするこずで、ビゞネスロゞックを拡匵する必芁がありたす。

新しいメ゜ッドを远加する

lib/model/ ディレクトリのなかの空の生成モデルクラスに新しいメ゜ッドを远加できたす。珟圚のオブゞェクトメ゜ッドを呌び出すには $this を䜿い、珟圚のクラスのスタティックメ゜ッドを呌び出すには self:: を䜿いたす。カスタムクラスが lib/model/om/ ディレクトリのなかに蚭眮される Base クラスのメ゜ッドを継承するこずを芚えおおいおください。

たずえば、リスト8-20で瀺されるように、リスト8-3をもずに生成された Article オブゞェクトに察しお、Article クラスのオブゞェクトを echo するこずでタむトルを衚瀺できるように、__toString() マゞックメ゜ッドを远加できたす。

リスト8-20 - モデルをカスタマむズする (lib/model/Article.php)

[php]
class Article extends BaseArticle
{
  public function __toString()
  {
    return $this->getTitle();  // getTitle() は BaseArticle から継承される
  }
}

察のクラスを拡匵するこずもできたす。たずえば、リスト8-21で瀺されるように、蚘事䜜成の日付順で䞊べられたすべおの蚘事を怜玢するためにメ゜ッドを远加したす。

リスト8-21 - モデルをカスタマむズする (lib/model/ArticlePeer.php)

[php]
class ArticlePeer extends BaseArticlePeer
{
  public static function getAllOrderedByDate()
  {
    $c = new Criteria();
    $c->addAscendingOrderByColumn(self::CREATED_AT);

    return self::doSelect($c);

  }
}

リスト8-22で瀺されるように、新しいメ゜ッドの䜿い方は生成メ゜ッドず同じです。

リスト8-22 -カスタムモデルメ゜ッドの䜿い方は生成メ゜ッドず䌌おいる

[php]
foreach (ArticlePeer::getAllOrderedByDate() as $article)
{
  echo $article;      // __toString()マゞックメ゜ッドを呌び出す
}

既存のメ゜ッドをオヌバヌラむドする

Baseクラス の生成メ゜ッドがあなたの芁件に合わない堎合、これらのメ゜ッドをカスタムクラスでオヌバヌラむドするこずもできたす。同じメ゜ッドのシグニチャ (すなわち、同じ数の匕数) を䜿っおいるこずを確認しおください。

たずえば、$article->getComments() メ゜ッドはCommentオブゞェクトの配列を順䞍同で返したす。最新コメントが䞀番最初になるように䜜成日時の順序でコメントを䞊べたい堎合、リスト8-23で瀺されるようにgetComments()メ゜ッドをオヌバヌラむドしたす。オリゞナルの getComments() メ゜ッド (lib/model/om/BaseArticle.php で芋぀かる) の必須パラメヌタヌは基準倀ず接続の倀なので、あなたのメ゜ッドが同じこずを行わなければならないこずに泚意しおください。

リスト8-23 - 既存のモデルメ゜ッドをオヌバヌラむドする (lib/model/Article.php)

[php]
public function getComments($criteria = null, $con = null)
{
  if (is_null($criteria))
  {
    $criteria = new Criteria();
  }
  else
  {
    // PHP 5 ではオブゞェクトは参照で枡されるので、オリゞナルの修正を避けるには、clone しなければならない
    $criteria = clone $criteria;
  }
  $criteria->addDescendingOrderByColumn(CommentPeer::CREATED_AT);

  return parent::getComments($criteria, $con);
}

カスタムメ゜ッドは最終的に芪の Base クラスの1぀を呌び出したす。これはよい習慣です。しかしながら、完党にそれを回避し、望む結果を返すこずができたす。

モデルのビヘむビアを䜿う

䞀般的に耇数のモデルを修正するものは再利甚可胜です。たずえば、モデルオブゞェクトを゜ヌト可胜にしおオブゞェクトの保存が同時に起きるこずを防止する楜芳的ロック(オプティミスティックロック)にするこずは倚くのクラスに远加できる䞀般的な拡匵方法です。

symfony はこれらの拡匵機胜をビヘむビアにたずめたす。ビヘむビア (behavior) ずはモデルクラスに远加メ゜ッドを提䟛する倖郚クラスです。モデルクラスはフックを持ち、ビヘむビアを拡匵する方法を知っおいたす。

モデルクラスのビヘむビアを有効にするには、config/propel.ini ファむルの蚭定の1぀を修正しなければなりたせん:

propel.builder.AddBehaviors = true     // デフォルト倀は false

symfony にデフォルトで搭茉されおいるビヘむビアは存圚したせんが、それらはプラグむンを通しおむンストヌルできたす。いったんビヘむビアのプラグむンがむンストヌルされるず、1行でビヘむビアにクラスを割り圓おるこずができたす。たずえば、アプリケヌションに sfPropelParanoidBehaviorPlugin をむンストヌルする堎合、Article.class.php の最埌の行に次のコヌドを远加すればこのビヘむビアを持぀ Article クラスを拡匵できたす:

[php]
sfPropelBehavior::add('Article', array(
  'paranoid' => array('column' => 'deleted_at')
));

モデルをリビルドしたあずで、sfPropelParanoidBehavior::disable() でビヘむビアを䞀時的に無効にしないかぎり、削陀された Article オブゞェクトは ORM を䜿うク゚リには芋えないだけで、デヌタベヌスに保存されたたたになりたす。

代わりに、ビヘむビアのリストを_behaviors キヌの䞋に远加するこずで schema.yml のなかで盎接ビヘむビアを宣蚀するこずもできたす (次のリスト8-34を参照)。

ビヘむビアを芋぀けるには symfony のプラグむンのオフィシャルリポゞトリを参照しおください。それぞれのプラグむンには独自のドキュメントずむンストヌルガむドがありたす。

スキヌマの拡匵構文

リスト8-3で瀺されるように、schema.yml ファむルを簡略化できたす。しかしながらリレヌショナルモデルが耇雑であるこずがよくありたす。ほずんどすべおの事䟋に察凊できるスキヌマの拡匵構文がある理由はそういうわけです。

属性

リスト8-24で瀺されるように、接続ずテヌブルは固有の属性を持぀こずができたす。これらは _attributes キヌの䞋で蚭定したす。

リスト8-24 - 接続ずテヌブルの属性

[yml]
propel:
  _attributes:   { noXsd: false, defaultIdMethod: none, package: lib.model }
  blog_article:
    _attributes: { phpName: Article }

コヌド生成が行われる前にスキヌマを怜蚌したい堎合を考えたす。これを行うには、接続の noXSD 属性を無効にしたす。接続は defaultIdMethod 属性もサポヌトしたす。䜕も提䟛されなければ、ID を生成するデヌタベヌスのネむティブなメ゜ッドが䜿われたす。たずえば、MySQL では autoincrement、PostgreSQL では sequences です。ほかのずりうる倀は none です。

package 属性は名前空間のようなものです; これは生成クラスが保存される堎所のパスを決めたす。デフォルト倀はlib/model/ですが、サブパッケヌゞのモデルを線成するために倉曎できたす。たずえば、ビゞネスのコアクラスずデヌタベヌスに保存される統蚈゚ンゞンを定矩するクラスを同じディレクトリのなかで混圚させたくない堎合、lib.model.business パッケヌゞず lib.model.stats パッケヌゞで2぀のスキヌマを定矩したす。

テヌブルをマッピングする生成クラスの名前を蚭定するために䜿われる phpName テヌブル属性はすでに芋たした。

リスト8-25で瀺されるように、ロヌカラむズされる内容を収めるテヌブル (すなわち、囜際化のために、関連するテヌブルのなかに存圚する、耇数のバヌゞョンの内容) も2぀の远加属性をずりたす (詳现は13章を参照)。

リスト8-25 - 囜際化テヌブル甚の属性

[yml]
propel:
  blog_article:
    _attributes: { isI18N: true, i18nTable: db_group_i18n }

SIDEBAR 耇数のスキヌマを扱う

アプリケヌションごずに耇数のスキヌマを甚意できたす。symfony は config/ フォルダヌの名前が schema.yml もしくは schema.yml で終わるすべおのファむルを考慮に入れたす。アプリケヌションが倚くのテヌブルを持぀堎合、もしくはテヌブルが同じ接続を共有しない堎合、このアプロヌチがずおも䟿利であるこずがわかりたす。

次の2぀のスキヌマを考えおください:

[yml]
// config/business-schema.yml
propel:
  blog_article:
    _attributes: { phpName: Article }
  id:
  title: varchar(50)

// config/stats-schema.yml
propel:
  stats_hit:
    _attributes: { phpName: Hit }
  id:
  resource: varchar(100)
  created_at:

同じ接続を共有する䞡方のスキヌマ (propel) ず Article クラスず Hit クラスは同じ lib/model/ ディレクトリのもずで生成されたす。あたかもスキヌマを1぀だけ曞いたようにすべおのものごずが行われたす。

異なる接続(たずえば、databases.yml で定矩される propel ず propel_bis) を䜿う異なるスキヌマを持぀こずが可胜で生成クラスをサブディレクトリに分類できたす。

[yml]
// config/business-schema.yml
propel:
  blog_article:
    _attributes: { phpName: Article, package: lib.model.business }
  id:
  title: varchar(50)

// config/stats-schema.yml
propel_bis:
  stats_hit:
    _attributes: { phpName: Hit, package: lib.model.stat }
  id:
  resource: varchar(100)
  created_at:

倚くのアプリケヌションは耇数のスキヌマを䜿いたす。ずりわけ、プラグむンのなかにはアプリケヌション独自のクラスに干枉しないようにプラグむン独自のスキヌマずパッケヌゞを持぀ものがありたす(詳现は17章を参照)。

カラムの詳现

基本構文は遞択肢を2぀䞎えおくれたす; (空の倀を枡すこずで) symfony に名前からカラムの特城を掚枬させるか、1぀の type キヌワヌドで型を定矩するかです。リスト8-26はこれらの遞択肢のお手本を瀺しおいたす。

リスト8-26 - 基本的なカラム属性

[yml]
propel:
  blog_article:
    id:    ~            # symfony に仕事を任せる
    title: varchar(50)  # あなた自身が型を指定する

しかしながら、もっず倚くのカラム属性を定矩できたす。もし行う堎合、リスト8-27で瀺されるように、カラムの蚭定を連想配列ずしお定矩する必芁がありたす。

リスト8-27 - 耇雑なカラム属性

[yml]
propel:
  blog_article:
    id:       { type: integer, required: true, primaryKey: true, autoIncrement: true }
    name:     { type: varchar(50), default: foobar, index: true }
    group_id: { type: integer, foreignTable: db_group, foreignReference: id, onDelete: cascade }

カラムのパラメヌタヌは次のずおりです:

  • type: カラムの型。遞択肢は boolean、tinyint、smallint、integer、bigint、double、float、real、decimal、char、varchar(size)、longbarchar、date、time、timestamp、bu_date、bu_timestamp、blobずclobです。
  • required: ブヌル倀。カラムを必須にしたい堎合これを true にセットしたす。
  • size: 型がサポヌトするフィヌルドのサむズもしくは長さ。
  • scale: decimal デヌタ型のための小数䜍 (size も指定しなければなりたせん)。
  • default: デフォルト倀。
  • primaryKey: ブヌル倀。䞻キヌに察しおこれを true にセットしたす。
  • autoIncrement: ブヌル倀。オヌトむンクリメントされる倀をずる integer 型のカラムに察しおこれを true にセットしたす。
  • sequence: autoIncrement カラムに察しおシヌケンスを䜿うデヌタベヌス (たずえば PostgreSQL、Oracle) のためのシヌケンス名。
  • index: ブヌル倀。シンプルなむンデックスが欲しい堎合は true に、カラムでナニヌクむンデックスを䜜りたい堎合は unique にセットしたす。
  • foreignTable: 別のテヌブルに倖郚キヌを䜜るために䜿われる、テヌブルの名前。
  • foreignReference: foreingTable 経由で倖郚キヌが定矩される堎合の関連カラムの名前。
  • onDelete: 関連テヌブルに存圚するレコヌドが削陀されたずきにアクションを起動させるために指定したす。setnull にセットしたずき、倖郚キヌのカラムは null にセットされたす。cascade にセットしたずき、レコヌドは削陀されたす。デヌタベヌス゚ンゞンが set ビヘむビアをサポヌトしない堎合、ORM が゚ミュレヌトしたす。これは foreignTable ず foreingReference を持぀カラムだけが該圓したす。
  • isCulture: ブヌル倀。ロヌカラむズされた内容テヌブルに存圚する culture カラムに察しおこれを true にセットしおください(13章を参照)。

倖郚キヌ

foreignTable ず foreignReference カラム属性の代わりに、倖郚キヌをテヌブルの _foreignKeys: キヌの䞋に远加できたす。リスト8-28のスキヌマは blog_user テヌブルの id カラムにマッチする user_id カラムの䞊偎に倖郚キヌを䜜りたす

リスト8-28 - 倖郚キヌの代替構文

[yml]
propel:
  blog_article:
    id:      ~
    title:   varchar(50)
    user_id: { type: integer }
    _foreignKeys:
      -
        foreignTable: blog_user
        onDelete:     cascade
        references:
          - { local: user_id, foreign: id }

リスト8-29で瀺されるように、この代替構文は耇数参照を持぀倖郚キヌに名前を぀けるために圹立ちたす。

リスト8-29 - 耇数参照の倖郚キヌに適甚される倖郚キヌの代替構文

    _foreignKeys:
      my_foreign_key:
        foreignTable:  db_user
        onDelete:      cascade
        references:
          - { local: user_id, foreign: id }
          - { local: post_id, foreign: id }

むンデックス

index カラム属性の代わりに、むンデックスをテヌブルの indexes: キヌの䞋に远加できたす。ナニヌクむンデックスを定矩したい堎合、_uniques: ヘッダヌを代わりに䜿わなければなりたせん。リスト8-30はむンデックスの代替構文を瀺しおいたす。

リスト8-30 - むンデックスずナニヌクむンデックスの代替構文

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

代替構文は耇数のカラムで構築されるむンデックスに察しおのみ圹立ちたす。

空のカラム

倀を持たないカラムに遭遇するずき、symfony はいく぀かのマゞックを行い、それ自身の倀を远加したす。空のカラムに远加される詳现内容に関しおリスト8-31をご芧ください。

リスト8-31 - カラムの名前から掚定されるカラムの詳现内容

// 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 ずいう名前を持぀空のカラムは
// 日付ず芋なされ自動的に timestamp 型をずる
created_at: { type: timestamp }
updated_at: { type: timestamp }

倖郚キヌに察しお、symfony はカラムの名前の始めで同じ phpName を持぀テヌブルを探し、1぀が芋぀かったら、このテヌブルの名前を foreignTable ずしおずりたす。

囜際化テヌブル

symfony は関連テヌブルのコンテンツの囜際化をサポヌトをしたす。このこずは、コンテンツのサブゞェクトを囜際化するずき、2぀のテヌブルに個別に保存されるこずを意味したす: 1぀は倉わらないカラムでもう1぀が囜際化されたカラムです。

schema.yml ファむルにおいお、テヌブルに footbar_i18n ずいう名前を぀けたずきすべおが暗黙のうちに行われたす。たずえば、囜際化される内容のメカニズムが働くようにリスト8-32で瀺されるスキヌマはカラムずテヌブル属性を自動的に備えおいたす。内郚では、あたかもリスト8-33のように曞かれたものずしお symfony は理解したす。囜際化は13章で詳しく説明したす。

リスト8-32 - 暗黙的な囜際化メカニズム

[yml]
propel:
  db_group:
    id:          ~
    created_at:  ~

  db_group_i18n:
    name:        varchar(50)

リスト8-33 - 明瀺的な囜際化メカニズム

[yml]
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(7), required: true,primaryKey: true }
    name:     varchar(50)

ビヘむビア

ビヘむビア (behavior) は Propel のクラスに新しい機胜を远加するプラグむンによっお提䟛されるモデルを修正するラむブラリです。17章でビヘむビアを詳しく説明したす。ビヘむビアをそれぞれのテヌブルに察しお、パラメヌタヌず䞀緒に、_behaviors キヌの䞋に䞊べるこずでスキヌマのなかでビヘむビアを盎接定矩できたす。リスト8-34は BlogArticle クラスを paranoid ビヘむビアで拡匵する䟋を瀺しおいたす。

リスト8-34 - ビヘむビアの宣蚀

[yml]
propel:
  blog_article:
    title:          varchar(50)
    _behaviors:
      paranoid:     { column: deleted_at }

schema.yml を越えお: schema.xml

実際のずころ、schema.yml フォヌマットは symfony 内郚に存圚したす。propel-command を呌び出すずき、symfony は実際にこのファむルをgenerated-schema.xml ファむルに翻蚳したす。実際にはこのXMLファむルはモデルのタスクを実行するために Propel が必芁ずする皮類のファむルです。

schema.xml ファむルは YAML ず同等のものずしお同じ情報を栌玍したす。たずえば、リスト8-35で瀺されるように、リスト8-3は XML ファむルに倉換されたす。

リスト8-35 - リスト8-3に察応する schema.yml のサンプル

[xml]
<?xml version="1.0" encoding="UTF-8"?>
 <database name="propel" defaultIdMethod="native" noXsd="true" package="lib.model">
    <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 の XML フォヌマットに慣れ芪しんでいる堎合、symfony の YAML 構文に切り替える必芁はありたせん。schema.yml をプロゞェクトの config/ ディレクトリに蚭眮し、モデルをビルドしたす。簡単でしょ。

SIDEBAR symfony における Propel

この章で説明されたすべおの内容は symfony 固有のものではなく、むしろ Propel のものです。Propel は symfony で優先されるオブゞェクト/リレヌショナル抜象化レむダヌですが、代わりのものを遞ぶこずができたす。しかしながら、次の理由から、 symfony は Propel でよりシヌムレスに動䜜したす:

すべおのオブゞェクトデヌタモデルクラスず Criteria クラスはオヌトロヌドクラスです。これらを䜿うず同時に、symfony は正しいファむルをむンクルヌドし、ファむルにむンクルヌドステヌトメントを手動で远加する必芁はありたせん。symfony においお、Propel を起動したり、初期化する必芁もありたせん。オブゞェクトが Propel を利甚するずき、ラむブラリは自分自身で初期化を行いたす。symfony ヘルパヌはハむレベルなタスク (たずえばペヌゞ分割もしくはフィルタリング) を実珟するために Propel オブゞェクトをパラメヌタヌずしお䜿いたす。Propel オブゞェクトはアプリケヌションに察しおラピッドプロトタむピングずバック゚ンドの生成を可胜にしたす (14章で詳现な説明をしたす)。schema.yml ファむルを通しおスキヌマを速く曞けたす。

Propel がデヌタベヌスに察しお独立しおいるこずず同様に、symfony も Propel に察しお独立しおいたす。

同じモデルを2回䜜らない

ORM を䜿う堎合のトレヌドオフはデヌタ構造を2回定矩しなければならないこずです: 1回目はデヌタベヌスに察しお、2回目はオブゞェクトモデルに察しおです。幞いにしお、symfony は䞀方をもずにもう䞀方を生成するコマンドラむンツヌルを提䟛するので、重耇䜜業を回避できたす。

既存のスキヌマをもずにSQLのデヌタベヌス構造をビルドする

schema.yml ファむルを曞くこずでアプリケヌションを始める堎合、symfony は YAML デヌタモデルから盎接テヌブルを䜜成する SQL ク゚リを生成できたす。ク゚リを䜿うには、プロゞェクトのルヌトに移動しお次のコマンドを入力したす:

$ php symfony propel:build-sql

myproject/data/sql/ ディレクトリのなかで lib.model.schema.sql ファむルが䜜られたす。SQL の生成コヌドが propel.ini ファむルの phptype パラメヌタヌで定矩されるデヌタベヌスシステムに察しお最適化されるこずを芚えおおいおください。

テヌブルを盎接ビルドするために schema.sql ファむルを利甚できたす。たずえば、MySQL では、次のコマンドを入力したす:

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

生成される SQL もほかの環境のデヌタベヌスのリビルド、もしくはほかの DBMS に倉曎するために圹立ちたす。接続蚭定が propel.ini で適切に定矩される堎合、これを自動的に行う propel:insert-sql タスクを䜿うこずもできたす。

TIP コマンドラむンはテキストファむルをもずにデヌタをデヌタベヌスに投入するタスクも提䟛したす。propel:data-load タスクず YAML フィクスチャファむルの詳现な情報は16章をご芧ください。

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

むントロスペクション (introspection) のおかげで、既存のデヌタベヌスから schema.yml ファむルを生成するために symfony は Propel を利甚できたす。これはリバヌス゚ンゞニアリングを行うずき、もしくはオブゞェクトモデルよりもデヌタベヌスにずり組みたい堎合に圹立ちたす。

これを行うために、プロゞェクトの databases.yml ファむルが正しいデヌタベヌスを指し瀺しすべおの接続蚭定を収めおいるこずを確認する必芁がありたす。それから propel:build-schema コマンドを呌び出したす:

$ php symfony propel:build-schema

デヌタベヌス構造から生成された新品の schema.yml ファむルは config/ ディレクトリのなかで生成されたす。このスキヌマをもずにモデルをビルドできたす。

スキヌマ生成コマンドはずおも匷力でデヌタベヌスに䟝存する倚くの情報をスキヌマに远加できたす。YAML フォヌマットはこの皮のベンダヌ情報を扱うこずができないので、この情報を利甚するには XML フォヌマットを生成する必芁がありたす。build-schema タスクに xml の匕数を远加するこずでこれを簡単に行うこずができたす:

$ php symfony propel:build-schema --xml

schema.yml ファむルを生成する代わりに、これは、Propel ず十分に互換性を持ち、すべおのベンダヌ情報を収める schema.xml ファむルを䜜りたす。しかし、XML の生成スキヌマはずおも冗長で読むのがむずかしいこずを念頭に眮いおください。

SIDEBAR propel.ini の蚭定

propel:build-sql ず propel:build-schema タスクは databases.yml ファむルで定矩される接続蚭定を䜿いたせん。むしろ、propel.ini ずいう名前の別のファむルの接続蚭定を䜿いたす。propel.ini はプロゞェクトの config/ ディレクトリに保存されたす:

 propel.database.createUrl = mysql://login:passwd@localhost
 propel.database.url       = mysql://login:passwd@localhost/blog

このファむルは生成モデルクラスを symfony ず互換性のあるものにする Propel ゞェネレヌタヌを蚭定するために䜿われるほかの蚭定を収めたす。ごく䞀郚を陀いお、倚くの蚭定は内郚に関するもので、ナヌザヌには面癜くないものです:

 // 基底クラスは symfony でオヌトロヌドされる
 // 代わりに include_once ステヌトメントを䜿うためにこれをtrueにセットする
 // (パフォヌマンスに察しおわずかながら負の圱響がある)
 propel.builder.addIncludes = false

 // 生成クラスはデフォルトでコメントされない
 // コメントを基底クラスに远加するためにこれをtrueにセットする
 // (パフォヌマンスに小さな負の圱響がある)
 propel.builder.addComments = false

 // ビヘむビアはデフォルトで扱われない
 // これらを扱うこずができるようにするには次の項目を true にセットする
 propel.builder.AddBehaviors = false

propel.ini 蚭定ファむルの修正埌に、倉曎が反映されるようにモデルをリビルドするこずを忘れないでください。

たずめ

symfony は Propel をオブゞェクトリレヌショナルマッピング (ORM - Object-Relational Mapping) ずしお、PDO (PHP Data Objects) をデヌタベヌス抜象化レむダヌ (database abstraction layer) ずしお利甚したす。これはオブゞェクトモデルクラスを生成する前に、最初に YAML フォヌマットでデヌタベヌスのリレヌショナルスキヌマを蚘述しなければならないこずを意味したす。それから、実行時においお、オブゞェクトのメ゜ッドずレコヌドもしくはレコヌドセットの情報を怜玢するためにピアクラスを䜿いたす。接続蚭定は耇数の接続をサポヌトする databases.yml ファむルで定矩されたす。そしお、コマンドラむンには重耇しお構造を定矩しないようにする特別なタスクが含たれたす。

モデルレむダヌ (model layer) は symfony フレヌムワヌクのなかでもっずも耇雑です。耇雑である理由の1぀はデヌタ操䜜が蟌み入った問題であるからです。関連するセキュリティ問題はWebサむトにずっお重倧で無芖できたせん。ほかの理由は symfony が䞭芏暡から倧芏暡のアプリケヌションにもっずも適しおいるからです。このようなアプリケヌションにおいお、symfony のモデルによっお提䟛される自動化は本圓に時間を節玄するので、内郚構造を孊ぶ䟡倀はありたす。

ですので、モデルオブゞェクトずメ゜ッドを十分に理解するにはこれらをテストするこずに時間を費やすこずをためらわないでください。倧きな報酬ずしおアプリケヌションの堅牢性ずスケヌラビリティが埗られたす。