LionHeart SD BLOG

株式会社ライオンハート システムデザインの技術ブログ

インストールしたPropelを実際に利用してみる(その3:Behaviors編[1])

こんにちは、株式会社ライオンハートの鵜飼です。

その1その2に引き続き、Propelについてのメモその3です。

今回は導入編・その1で少しだけ触れていた、schema.xmlに記述することで、Propel内で自動的に色々な便利な処理をしてくれるBehaviorsについてのメモです。

公式ドキュメントを読んで解釈していますが、英語スキルが貧弱なので間違っている所がありましたらご指摘下さい…。
並び順が公式ドキュメントから変更していますが、個人の独断と偏見による利用シーンが多そうで便利そうな順です。

Timestampable Behavior

created_atupdated_atというカラムを追加し、それぞれに"作成日時"、"更新日時"を自動的に登録してくれる設定です。

<table name="book">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" />
  <column name="title" type="varchar" required="true" primaryString="true" />
  <behavior name="timestampable" />
</table>

MySQLだとcreatedmodifiedというカラムを指定しておくと同じような処理をしてくれますが、(たしか)TIMESTAMP型じゃないとダメだったと思うので、他のDBでも動くようにしたりするなど、今後を考慮するとアプリケーション側で対処出来ると良さそうですね。

設定変更

また、下記のように設定を行うとcreated_atupdated_atカラム名は自由に変更することが可能です。

<table name="book">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" />
  <column name="title" type="varchar" required="true" primaryString="true" />
  <column name="my_create_date" type="timestamp" />
  <column name="my_update_date" type="timestamp" />
  <behavior name="timestampable">
    <parameter name="create_column" value="my_create_date" />
    <parameter name="update_column" value="my_update_date" />
  </behavior>
</table>

※ややこしいですが、type="timestamp"を指定すると、MySQL上ではDATETIME型になります。

Archivable Behavior

behaviorを指定したテーブルと同じ構造のアーカイブテーブルを作成し、レコードのアーカイブが出来るようにする設定です。

<table name="book">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" />
  <column name="title" type="varchar" required="true" primaryString="true" />
  <behavior name="archivable" />
</table>

「アーカイブ」と考えると利用シーンがあまり思い浮かびませんが、Propelではこの仕組みを利用して論理削除を実現させています。

具体的には、archivableのbehaviorを設定するとdeleteメソッドを実行した際にアーカイブテーブルに削除するレコードを複製してから削除する流れになり、一般的な論理削除の処理であるフラグを立てる、とは異なり別のテーブルに残る仕組みとなります。

f:id:lh-sukai:20151221181833p:plain

コチラの利点としては、間違って削除フラグのフィルタを設定し忘れることがない、利用しないリソースがテーブル内に残らないので、もしかすると処理的にも優しいのかもしれない、という感じでしょうか。
削除フラグの場合、アプリケーション内のモデルやORMを利用している間は大丈夫でしょうが、SQLを直接実行した時は忘れがちなので、地味にリスクヘッジになりそうな予感がします。

設定変更

また、論理削除だけの利用ではなく、下記の設定を追記すると挿入時・編集時にもアーカイブテーブルに複製ができるようになるので、他の使いみちもありそうな気がします。

<table name="book">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" />
  <column name="title" type="varchar" required="true" primaryString="true" />
  <behavior name="archivable">
    <parameter name="archive_on_insert" value="false" />
    <parameter name="archive_on_update" value="false" />
    <parameter name="archive_on_delete" value="true" />
  </behavior>
</table>

Delegate Behavior

通常のJOIN(foreign-key)に似たような機能ですが、delegateで指定した先のテーブルのカラムも直接取り扱うことが出来るようになる設定です。

<table name="account">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" />
  <column name="login" type="varchar" required="true" />
  <column name="password" type="varchar" required="true" />
  <behavior name="delegate">
    <parameter name="to" value="profile" />
  </behavior>
</table>
<table name="profile">
  <column name="email" type="varchar" />
  <column name="telephone" type="varchar" />
</table>

言葉で書くだけだと分かりづらいですが…サンプルコードを見ていただくとイメージ出来るかと思います。

<?php
$account = new Account();

$account->setLogin('login');
$account->setPassword('password');

// delegateで指定した先のテーブルのカラムも利用できる
$account->setEmail('info@example.com');
$account->setTelephone('00-0000-0000');

$account->save();

つまり下記と同等の記述が上記のように書けるようになります。

<?php
$account = new Account();

$account->setLogin('login');
$account->setPassword('password');

// Profileを設定して代入する
$profile = new Profile();
$profile->setEmail('info@example.com');
$profile->setTelephone('00-0000-0000');
$account->setProfile($profile);

$account->save();

Many-To-OneのDelegate

正直、上記のようなOne-To-One構造のリレーションだと、ちょっと記述量が減るだけで殆ど意味をなさないかと思いますが、Many-To-One構造のリレーションでも利用する事が出来ます。

<table name="player">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" />
  <column name="first_name" type="varchar" />
  <column name="last_name" type="varchar" />
</table>
<table name="basketballer">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" />
  <column name="points" type="integer" />
  <column name="field_goals" type="integer" />
  <column name="three_points_field_goals" type="integer" />
  <column name="player_id" type="integer" />
  <foreign-key foreignTable="player">
      <reference local="player_id" foreign="id" />
    </foreign-key>
  <behavior name="delegate">
    <parameter name="to" value="player" />
  </behavior>
</table>

上記のように設定を行うと、playerの中にはbasketballerもいれば、他のplayerも居ると思いますが、basckerballerを登録する時は何も気にせず登録することが出来ます。

<?php
$basketballer = new Basketballer();

$basketballer->setPoints(100);
$basketballer->setFieldGoals(35);
$basketballer->setThreePointsFieldGoals(10);

// そのままplayerテーブルの情報も登録します
$basketballer->setFirstName('Foo');
$basketballer->setLastName('Bar');

「無くてもできるけど気にせず書ける」という所が利点でしょうか、便利にはなりそうな感じはしますね。

複数指定

また、delegate先はカンマ区切りで同時に複数指定することが出来ます。

<table name="account">
  <column name="id" required="true" primaryKey="true" autoIncrement="true" type="integer" />
  <column name="login" type="varchar" required="true" />
  <column name="password" type="varchar" required="true" />
  <behavior name="delegate">
    <parameter name="to" value="profile, preference" />
  </behavior>
</table>

Aggregate Column Behavior

JOINしたテーブルに対して、COUNTやSUM、AVG等の関数を実行した結果を、実行結果に含める事が出来る設定です。

<table name="post">
  <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
  <column name="title" type="varchar" required="true" primaryString="true" />
  <behavior name="aggregate_column">
    <parameter name="name" value="nb_comments" />
    <parameter name="foreign_table" value="comment" />
    <parameter name="expression" value="COUNT(id)" />
  </behavior>
</table>
<table name="comment">
  <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
  <column name="post_id" type="integer" />
  <foreign-key foreignTable="post" onDelete="cascade">
    <reference local="post_id" foreign="id" />
  </foreign-key>
</table>

この設定に関しては、実際のサンプルコードを見た方が分かりやすそうな気がしますね。
上記のように設定をすると、

<?php
$post = PostQuery::create()->findPK($id);

// $idのレコードに紐づくcommentテーブルの行数(COUNT結果)を取得できる
$post->getNbComments();

という感じになります。

上記例の様な、投稿に対するコメント数等、毎回必要な値があったりする際は便利そうな機能ですね。

雑感

色々と便利な機能があって、数行書くだけで色々な箇所に追記しないと行けないような事を付加することが出来るのは便利ですね。

まだ他の機能はあるのですが、今回は一旦ここまで…次回続きを書いていこうかと思います。