Propel 1.4のWhatsNewの超訳
この前のsymfony1.4勉強会では、doctrineばっかり言及されててPropelはあんまり触れられてませんでした。1.0から使ってる人はPropelに慣れきってると思うし、あと1.4の情報知らない人が多いのかな、と思って新要素のページを超訳してみたので公開しておきます。*1
翻訳元のページは http://propel.phpdb.org/trac/wiki/Users/Documentation/1.4/WhatsNew です。
What's new in Propel 1.4?
Propel1.4は1.3と後方互換性を保ったままのアップグレードになります。多くのバグフィックス、面白い新要素、速度改善などがあります。アップグレードした後はモデルの再ビルドを忘れないようにしようね。
save()メソッド、delete()メソッドでPre, Postフックをサポート
オブジェクトとして生成された save() と delete() メソッドは簡単にオーバーライドできるよ。実際にはPropelは必要となったときに下に書いてるメソッドの中から探してくるよ。
<?php preInsert() // code executed before insertion of a new object postInsert() // code executed after insertion of a new object preUpdate() // code executed before update of an existing object postUpdate() // code executed after update of an existing object preSave() // code executed before saving an object (new or existing) postSave() // code executed after saving an object (new or existing) preDelete() // code executed before deleting an object postDelete() // code executed after deleting an object
例えば、下の例だとINSERTする前に created_at カラムを必ず更新してくれるよ。
<?php class Book extends BaseBook { public function preInsert(PropelPDO $con = null) { $this->setCreatedAt(time()); } }
この新しい要素は書き込み操作にちょっとオーバーヘッドが出るから、必要ないと思うなら設定ファイルからfalseにしてね。
# ------------------- # TEMPLATE VARIABLES # ------------------- propel.addHooks = false
ビヘイビア
モデル間のビヘイビアをまとめたり、再利用できるようになったよ。
例えばオブジェクトが生成された日時や最新の更新日時をキープするにはシンプルにテーブル定義に timestampable ビヘイビアを定義するだけでできるよ。
<table name="book"> <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" /> <column name="title" type="VARCHAR" required="true" /> <behavior name="timestampable" /> </table>
モデルを再ビルドして、bookテーブルが自動で生成日時と更新日時をセットする2つの新しいカラムを持ってることを確認しよう。
<?php $b = new Book(); $b->setTitle('War And Peace'); $b->save(); echo $b->getCreatedAt(); // 2009-10-02 18:14:23 echo $b->getUpdatedAt(); // 2009-10-02 18:14:23 $b->setTitle('Anna Karenina'); $b->save(); echo $b->getCreatedAt(); // 2009-10-02 18:14:23 echo $b->getUpdatedAt(); // 2009-10-02 18:14:25
Propelビヘイビアはビルド時間が発生するけど、オーバーヘッドはほとんど無いと思っていいよ。スキーマファイルを編集すれば、Propelがメソッドを変更してモデルとPeerクラスの両方にメソッドを追加してくれるよ。HowTos セクションも読んでね。
Propel 1.4はtimestampable や soft_deleteといった、いくつかのビヘイビアをバンドルしてるよ。詳しくはドキュメントを読んでね。
マルチプル条件のJOIN
addMultipleJoin() メソッドを使うといくらでもJOINできちゃうよ。
<?php $c = new Criteria(); $c->addMultipleJoin(array( array(ReaderFavoritePeer::BOOK_ID, BookOpinionPeer::BOOK_ID), array(ReaderFavoritePeer::READER_ID, BookOpinionPeer::READER_ID)) Criteria::INNER_JOIN);
// SQL result SELECT ... FROM reader_favorite INNER JOIN book_opinion ON (reader_favorite.BOOK_ID = book_opinion.BOOK_ID AND reader_favorite.READER_ID = book_opinion.READER_ID)
複雑なJOINができるように各JOINの条件ごとに第3引数を追加できるよ。
<?php $c = new Criteria(); $c->addMultipleJoin(array( array(Book::USER_ID, UserPeer::ID)) array(UserPeer::RANK, 12, Criteria::GREATER_THAN), Criteria::LEFT_JOIN);
// SQL result SELECT ... FROM book LEFT JOIN user ON (book.USER_ID = user.ID AND user.RANK > 12)
複雑なJOINするのによく知られた方法だった、addJoin() メソッドの引数に配列を使うのは非推奨になったので注意しようね。
フルクエリロギング
Propelはバインドされたパラメータ付きでデータベースへの全てのクエリをロギングしてるよ。だから、Propelのログから簡単にコピーしたり、結果を見ながらデータベースへ実行したりできるよ。
それに加えて、各クエリごとにかかった時間や消費したメモリを記録するから、slowクエリだけや設定した閾値を超えたクエリだけロギングできるよ。
CreoleからPDOにスイッチした影響で、Propel1.3ではこのパワフルなロギングは無くなっちゃってたんだ。1.4からは1.2みたいなロギングができるようになってるよ。
詳しくはフルクエリロギングのドキュメントをチェックしてね。
Baseオブジェクトに__toString()
スキーマファイルでprimaryStringをカラムに定義しておくと、Propelがモデルオブジェクトに __toString() メソッドを作ってくれるよ。
<table name="book" phpName="Book"> .. <column name="title" type="varchar" size="125" primaryString="true" /> .. </table>
モデルをビルドした後、作られた BaseBook クラスは下記のマジックメソッドを提供してくれるよ。
<?php public function __toString() { return (string) $this->getTitle(); }
これはBookオブジェクトが返す文字列が titleカラムの値になる、ということを意味してるよ。
<?php $book = new book(); $book->setTitle('War And Peace'); echo $book; // 'War And Peace'
新しいPeer定数: OM_CLASS
Peerクラスに関連するモデルクラスを取得したい、と思ったことはない? そうしたいときは、パッケージの情報が一緒に含まれちゃってる定数を使うしかなかったんじゃないかなと思うよ。
<?php // in BaseBookPeer.php /** A class that can be returned by this peer. */ const CLASS_DEFAULT = 'bookstore.Book';
この定数は役に立つんだけど、オートロードに使えないんだ。実際に使うときはパッケージの情報を削除する文字列操作が必要になるよね。このめんどくさい処理はPropel 1.4だと必要なくなったよ。Peerクラスの新しい定数を使おうね。
<?php // in BaseBookPeer.php /** the related Propel class for this table */ const OM_CLASS = 'Book';
作られたPeerメソッドはこの定数を使えば、文字列操作しなくていいよ。だからちょっとだけ処理が速くなると思うよ。
PropelPagerは Countableとイテレータインターフェースを実装
Propelはペジネート用のクラスを提供してくれてるのは知ってるかな?もっと高性能になって、Countableとイテレータインターフェースをサポートするようになったよ。PropelPagerオブジェクトを配列のように操作できるようになってるよ。
<?php $c = new Criteria(); $c->add(BookPeer::AUTHOR, $authorId); $pager = new PropelPager($c, 'BookPeer', 'doSelect', $page = 1, $rowsPerPage = 20); if(count($pager)) // if the current page has results { foreach($pager as $book) // get pager results and iterate on them { echo $book->getTitle(); } }
PropelPagerの有用性については新しいドキュメントのHowTosセクションに書いてあるからチェックしてみようね。
実行時の Introspection をより良く
いくつかのメソッドはデータベースのリレーションシップで記述される新しい RelationMapクラスであると同時に、Mapクラスに追加され、実行時の introspection を緩和させてるよ。
TableMap DatabaseMap::getTableByPhpName($name) // TableMap object by object model name, e.g. 'Book' ColumnMap DatabaseMap::getColumn($name) // ColumnMap object by fully qualified name, e.g. book.AUTHOR_ID Array TableMap::getPrimaryKeys() // List of the ColumnMap objects corresponding to the table primary keys Array TableMap::getForeignKeys() // List of the ColumnMap objects corresponding to the table foreign keys Array TableMap::getRelations() // List of the table relationships, as RelationMap objects String TableMap::getPackage() // Package of the table mixed ColumnMap::getDefaultValue() // Default value defined in the schema for this column TableMap ColumnMap::getRelatedTable() // Related TableMap object by foreign key ColumnMap ColumnMap::getRelatedColumn() // Related ColumnMap object by foreign key RelationMap ColumnMap::getRelation() // Related RelationMap object integer RelationMap::getType() // RelationMap::ONE_TO_MANY, RelationMap::MANY_TO_ONE, or RelationMap::ONE_TO_ONE string RelationMap::getOnDelete() // ON DELETE directive, e.g. 'SET NULL' TableMap RelationMap::getLocalTable() // Local TableMap object (the one bearing the fkey) TabkeMap RelationMap::getForeignTable() // Foreign TableMap object Array RelationMap::getColumnMappings() // List of local => foreign column for this relation, e.g array('book.PUBLISHER_ID' => 'publisher.ID') Array RelationMap::getLocalColumns() // List of the ColumnMap objects on the local side of the relation Array RelationMap::getForeignColumns() // List of the ColumnMap objects on the foreign side of the relation
HowTos セクションにある新しい Runtime Introspection のドキュメントをチェックしてみてね。
あと、PeerクラスからPropelオブジェクトクラスの名前を簡単にゲットできるよ。
echo BookPeer::getOMClass() => 'bookstore.Book' echo BookPeer::getOMClass($withPrefix = false) => 'Book'
MapBuilders はなくなりました
Runtime introspection は以前は MapBuildersと呼ばれるTablemapオブジェクトを runtimeのビルダークラスとして依存してたよ。MapBuildersクラスはもう作られなくなってるんだ。代わりに、簡単なTableMapクラスを使ってるよ。
TableMapをゲットする普通の方法は以前と同じようにPeerクラスを通して行うよ。
<?php $bookTableMap = BookPeer::getTableMap();
けど、手動でのTableMapsのビルドはもっと簡単になってるよ。
<?php // Propel 1.3 way to initialize a TableMap $className = 'Book'; $mapBuilderClass = $className . 'MapBuilder'; $mapBuilder = new $mapBuilderClass(); if (!$mapBuilder->isBuilt()) { $mapBuilder->doBuild(); } $bookTableMap = $databaseMap->getTable('book'); // Propel 1.4 way to initialize a TableMap $className = 'Book'; $tableMapClass = $className . 'TableMap'; $bookTableMap = $databaseMap->addTableFromMapClass($tableMapClass);
TableMapsをロードして関連テーブルを引っ張ってくるには、下のように書く必要があることを覚えておこうね。
<?php // build all the TableMap objects of tables related to Book $relations = $bookTableMap->getRelations();
新しいビルドオプション
作られるクラスを上手くコントロールできるように、3つの新しい設定がbuild.propertiesに追加されてるよ。
propel.addValidateMethod = {true}|false
クラスにvalidate() メソッドを追加するかどうかを設定するよ。falseにするとPropelバリデーションは使わなくなるよ。
propel.addIncludes = {true}|false
作られるスタブクラスで必要とするステートメントを追加するかどうかを設定するよ。falseにすると、実行時に各クラスがオートロードされるよ。
propel.addHooks = {true}|false
save()メソッドやdelete()メソッドでの pre- や post- フックをサポートするかどうかを設定するよ。falseにするとフックは使えなくなるけど少し処理スピードが上がるよ。
外部キーの取得にはインスタンスプールを利用
外部キーを経由して参照してるモデルから関連するオブジェクトを取得するときは、大抵の場合、下記のようなPropelが作るメソッドを使うよ。
<?php $author = $book->getAuthor(); // $author is an Author instance
裏では、このメソッドはbookオブジェクトのauthor_idプロパティを使って、データベースにauthorレコードを取得するクエリを作って、返す値としてAuthorオブジェクトをhydrateするよ。 もしスクリプトを実行する以前にauthorオブジェクトがhydrateされてたどうなると思う? インスタンスプールのおかげで、Propelはメモリにオブジェクトを覚えておくことができるよ。たくさんのデータベースクエリを保存することができるんだ。例えば
<?php foreach($author->getBooks() as $book) { echo $book->getTitle(); echo $book->getAuthor()->getName(); }
Propel1.3だと、このコードは データベースに n+1 回クエリを投げることになってたよ(n は authorが書いた本の数になるよ)。 だけど、Propel 1.4は1回のクエリで実行できちゃうよ。getAuthor() メソッドが呼ばれると、Propelは参照する本のAuthorをメモリから参照するよ。だから、データベースクエリやhydrateするプロセスは全部スキップしちゃうよ。結果はほんの少しの速度向上になって、1ページで実行されるクエリの数を減らすことになるよ。
BasePeer::populateStmtValues() は publicになります
自作のSQLクエリやBasePeer::createSelectSql() メソッドが返すクエリを編集して使えるよ。クエリへ値をセットするのにはPropelのバインド機能使ってるよ。Propelはバインドすることでカラムの型のPDOを使うんだ。だから、このメソッドは少しイライラさせちゃうかもしれないよ。
<?php $sql = BasePeer::createSelectSql($criteria, $params); $sql = "INSERT INTO temp_table_name $sql"; $stmt = $con->prepare($sql); BasePeer::populateStmtValues($stmt, $params, $dbMap, $db); $stmt->execute();
ドキュメントの再編成
PropelのドキュメントはSubversionのリポジトリに今あるよ。簡単に編集できるし、ドキュメントの変遷をトレースするのも簡単だよ。修正チケットも添付してるし、自分でパッケージングしたPropelライブラリをバンドルしたりできるよ。
もちろん、上で記述されてる全ての変更点については反映したドキュメントがアップデートされてるよ。
*1:ホントに斜め読みなので気になる人は原文チェックしてください。