前回までで、プロジェクトとモジュールの構築が完了しました。
今度はEntityとDaoの順番でコンポーネントを作成していきます。
で、せっかくなのでSQLiteを使って、実際にRDBMSにアクセスしてみます。
今回はamfphpプラグインのQuickStartで使ったDBを使います。
テーブル構造はQuickStartのOverviewにあります。
それからDBファイルは、QuickStartのDataBaseからダウンロードできます。
DataBaseの準備
先程のURLからダウンロードしたSQLiteのDBファイルをsampleプロジェクトのvar/dbディレクトリにコピーします。
更新処理も試しますので、コピーしたファイルに書き込み権限をつけてください。
次にDBの接続設定を行います。
S2Base.PHP5では、DBへの接続設定をS2Daoのpdo.diconで行います。
pdo.diconはsampleプロジェクトのapp/commons/daoディレクトリにあります。
pdo.diconは、こんな風に記載されていると思います。
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN" "http://www.seasar.org/dtd/components.dtd"> <components namespace="pdo"> <!-- <component name="dataSource" class="S2Container_PDODataSource"> <property name="dsn">"mysql:host=localhost; dbname=s2con"</property> <property name="user">"root"</property> <property name="password">"pass"</property> <property name="option"> array(PDO::ATTR_ORACLE_NULLS => PDO::NULL_EMPTY_STRING, PDO::ATTR_AUTOCOMMIT => true); </property> </component> --> <component name="dataSource" class="S2Container_PDODataSource"> <property name="dsn">return 'sqlite:' . S2BASE_PHP5_VAR_DIR . '/db/s2base.db'</property> </component> <!-- <component name="dataSource" class="S2Container_PDODataSource"> <property name="dsn">"pgsql:host=localhost; dbname=s2con"</property> <property name="user">"root"</property> <property name="password">"pass"</property> <property name="option"> array(PDO::ATTR_ORACLE_NULLS => PDO::NULL_EMPTY_STRING, PDO::ATTR_AUTOCOMMIT => false); </property> </component> --> <!-- <component name="dataSource" class="S2Container_PDODataSource"> <property name="dsn">"firebird:host=localhost; dbname=/path/to/s2con.fdb"</property> <property name="user">"root"</property> <property name="password">"pass"</property> <property name="option"> array(PDO::ATTR_ORACLE_NULLS => PDO::NULL_EMPTY_STRING, PDO::ATTR_AUTOCOMMIT => false); </property> </component> --> <!-- <component name="dataSource" class="S2Container_PDODataSource"> <property name="dsn">"oci:dbname=//localhost/s2con"</property> <property name="user">"root"</property> <property name="password">"pass"</property> <property name="option"> array(PDO::ATTR_ORACLE_NULLS => PDO::NULL_EMPTY_STRING, PDO::ATTR_AUTOCOMMIT => false); </property> </component> --> <!-- freetds dsn sybase|mssql --> <!-- <component name="dataSource" class="S2Container_PDODataSource"> <property name="dsn">"dblib:host=localhost; dbname=s2con"</property> <property name="user">"root"</property> <property name="password">"passpass"</property> <property name="option"> array(PDO::ATTR_ORACLE_NULLS => PDO::NULL_EMPTY_STRING); </property> </component> --> <component name="requiredTx" class="S2Dao_RequiredInterceptor" /> <component name="requiresNewTx" class="S2Dao_RequiresNewInterceptor" /> <component name="mandatoryTx" class="S2Dao_MandatoryInterceptor" /> <component name="neverTx" class="S2Dao_NeverInterceptor" /> <component name="notSupportedTx" class="S2Dao_NotSupportedInterceptor" /> </components>今回は先程ダウンロードしたsample.dbを使いますので、pdo.diconを以下の様に修正します。
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN" "http://www.seasar.org/dtd/components.dtd"> <components namespace="pdo"> <component name="dataSource" class="S2Container_PDODataSource"> <property name="dsn">return 'sqlite:' . S2BASE_PHP5_VAR_DIR . '/db/sample.db'</property> </component> <component name="requiredTx" class="S2Dao_RequiredInterceptor" /> <component name="requiresNewTx" class="S2Dao_RequiresNewInterceptor" /> <component name="mandatoryTx" class="S2Dao_MandatoryInterceptor" /> <component name="neverTx" class="S2Dao_NeverInterceptor" /> <component name="notSupportedTx" class="S2Dao_NotSupportedInterceptor" /> </components>以上で、DBの接続設定は完了です。
Entity作成
続いてデータ型となるEntityを作成します。
sample.dbには、s2base_pluginというテーブルがあって、idとnameというカラムがあります。
このテーブルのデータを表現するS2BasePluginというEntityを作ります。
まずはsampleプロジェクトディレクトリでs2baseコマンドを実行して、コンソールを起動します。
% s2baseコマンドのリストが表示されたら、Entityコマンドを表す3を入力してエンターキーを押したら、
以下の様に質問に答えていきます。
[ Module list ] 0 : (exit) 1 : sample_module choice ? : 1 <-sample_moduleを表す1を入力してエンターキー use database ? (y/n) : y <-yを入力してエンターキー [ table list ] 0 : (exit) 1 : s2base_plugin 2 : sqlite_sequence choices ? (1,2,--,) : 1 <-s2base_pluginテーブルを表す1を入力してエンターキー entity class name ? [S2base_pluginEntity] : S2BasePlugin <-Entity名となるS2BasePluginを入力してエンターキー [ generate information ] module name : sample_module entity class name : S2BasePlugin table name : s2base_plugin columns : id, name confirm ? (y/n) : y <-yを入力してエンターキーuse database ?の質問にyと答えると、DB接続設定を使ってDBに接続してテーブルの一覧をリストしてくれます。
対象となるテーブルを選択してEntity名を指定したら、テーブルにあるカラムから自動的にEntityのプロパティを作成してくれます。
Entityはsampleプロジェクトのapp/modules/sample_module/entityディレクトリにS2BasePlugin.phpという名前で作成されて、以下の様な内容になっています。
<?php class S2BasePlugin { const TABLE = "s2base_plugin"; public function __construct(){} protected $id; const id_COLUMN = "id"; public function setId($val){$this->id = $val;} public function getId(){return $this->id;} protected $name; const name_COLUMN = "name"; public function setName($val){$this->name = $val;} public function getName(){return $this->name;} public function __toString() { $buf = array(); $buf[] = 'id => ' . $this->getId(); $buf[] = 'name => ' . $this->getName(); return '{' . implode(', ',$buf) . '}'; } /* private $prop; const prop_RELNO = 0; const prop_RELKEYS = 'this_fk:other_pk'; public function setProp(OtherEntity $entity){ $this->prop = $entity; } public function getProp(){ return $this->prop; } */ }constで宣言されている箇所がS2Daoの定数アノテーションです。
定数TABLEは、Entityとテーブルを関連付けるTABLEアノテーション
*_COLUMNは、Entityのプロパティとカラムを関連付けるCOLUMNアノテーションです。
Daoの作成
データ型となるEntityができましたので、次にEntityの永続化を行うDaoを作成します。
S2DaoではDaoはInterfaceとして作成し、実装はS2Dao+S2Containerによって自動的に作成されます。
S2BasePluginというEntityに対して、S2BasePluginDaoというDaoを作成します。
[ Command list ] 0 : (exit) 1 : dao 2 : dicon 3 : entity 4 : goya 5 : interceptor 6 : module 7 : service choice ? : 1 <-daoコマンドを表す1を入力してエンターキー [ Module list ] 0 : (exit) 1 : sample_module choice ? : 1 <-sample_moduleを表す1を入力してエンターキー use database ? (y/n) : y <-yを入力してエンターキー [ table list ] 0 : (exit) 1 : s2base_plugin 2 : sqlite_sequence choices ? (1,2,--,) : 1 <-s2base_pluginテーブルを表す1を入力してエンターキー dao interface name [S2base_pluginDao]? : S2BasePluginDao <-Dao名となるS2BasePluginDaoを入力してエンターキー entity class name ? [S2base_pluginEntity] : S2BasePlugin <-Entity名となるS2BasePluginを入力してエンターキー [ generate information ] module name : sample_module dao interface name : S2BasePluginDao dao test class name : S2BasePluginDaoTest entity class name : S2BasePlugin table name : s2base_plugin columns : id, name confirm ? (y/n) : y <-yを入力してエンターキーで、ここまで実行してまぬけな事態に気づく・・・。
DaoコマンドってEntityも作ってくれるんでした・・・^^;
前に実行したEntityコマンドって意味なかったですね・・・^^;;
えー、気を取り直して。
daoコマンドを実行すると、
DaoのInterfaceがapp/modules/sample_module/daoディレクトリに
Daoのテストクラスがtest/modules/sample_module/daoディレクトリに
そしてEntityがapp/modules/sample_module/entityディレクトリに作られます。
S2BasePluginDaoはこんな風になっています。
<?php interface S2BasePluginDao { const BEAN = "S2BasePlugin"; public function findAllList(); //public function findAllArray(); //public function update(S2BasePlugin $entity); //public function insert(S2BasePlugin $entity); //public function delete(S2BasePlugin $entity); }S2Daoでは、メソッドに規則に沿った名前をつけることで、メソッドの実体が作られます。
例えばS2BasePluginDaoのfindAllListは、更新系の命名規則になっていなくて
Listという文字でメソッド名が終わっていますので
検索メソッドとして実体が作られて、戻り値がS2Dao_ArrayList形式で戻ります。
この状態では、検索条件も指定されていないので
SELECT id, name FROM s2base_plugin;というSQLが実行された結果がS2BasePluginを格納したS2Dao_ArrayListとして帰ってくることになります。
このあたりはS2Daoのことになりますから、S2Daoをなでる時にでもまた少し。
今回は更新系の処理も行いたいので、以下の様にS2BasePluginDaoを変更します。
<?php interface S2BasePluginDao { const BEAN = "S2BasePlugin"; public function findAllList(); public function update(S2BasePlugin $entity); public function insert(S2BasePlugin $entity); public function delete(S2BasePlugin $entity); }
で、少し補足情報です。
前回作成されたsample_moduleの定義ファイル「sample_module.inc.php」に、
以下の様な記載があります。
S2ContainerApplicationContext::import(S2BASE_PHP5_ROOT . '/app/commons/dicon/dao.dicon'); S2ContainerApplicationContext::registerAspect('/Dao$/', 'dao.interceptor');S2DaoでDaoの機能を使用する為には、S2DaoInterceptorというInterceptorをDaoに対して適用する必要があります。
このS2DaoInterceptorが、実体の定義されていないInterfaceであるDaoに対して、メソッドの実装を提供するのです。
つまりいくらS2Daoの規則に沿ったDaoのInterfaceを定義しても、S2DaoInterceptorを適用しないとDaoとして動作しません。
というか実体がないんですから、S2Containerから取得できませんね。
実体を作ってないんですから、少し考えればわかりますよね。
フレームワークのお約束の手順をおまじないにしないことが、フレームワークをきちんと理解する第一歩だと思います。
・・・Delfinoごときがこういうことを言うのもなんですが^^;
さて、話は戻りますがS2ContainerApplicationContext::registerAspectです。
S2Containerの自動アスペクトという機能を使って
Daoという文字で終わる名前のコンポーネントに対して、dao.interceptorを適用しています。
dao.interceptorは、/app/commons/dicon/dao.diconで定義されているS2DaoInterceptorです。
S2Base1.0系では作成したDaoを定義したdiconでS2DaoInterceptorを適用していたのですが、
S2Base2.0系では、S2ContainerApplicationContextでimportしたクラスのうち
Daoという文字で終わるクラスに自動アスペクトでS2DaoInterceptorを適用しています。
これでdiconなしにDaoを定義できるのですが、
逆をかえせばDaoという文字で終わらないDaoを定義すると動作しないので注意です。
Daoという文字で終わらないDaoを定義したなら、diconファイルでアノテーションでS2DaoInterceptorを適用する必要があります。
今日はここまで
なんか無駄に冗長なことをしてしまいましたが、S2Base1.0系とS2Base2.0系の違いが少しでてきました。
次回はDaoのテストクラスを使ってUnitTestしてみます。