S2Base.PHP5をなでる(Service作成編)

前回は、EntityとDaoを作成しました。今回は、Serviceを作成してみましょう。
前回は、「次回はDaoのUnitTest」とか書いちゃいましたけど、ServiceのUnitTestということにしちゃいます。

Serviceの作成

sampleプロジェクトディレクトリでs2baseコマンドを実行して、コンソールを起動します。
そうしたら、以下の様に入力してS2BasePluginServiceという名前でServiceを作成してみましょう。

 
[ Command list ]
0 : (exit)
1 : dao
2 : dicon
3 : entity
4 : goya
5 : interceptor
6 : module
7 : service
choice ? : 7  <-serviceを表す7を入力してエンターキー
 
[ Module list ]
0 : (exit)
1 : sample_module
choice ? : 1  <-sample_moduleを表す7を入力してエンターキー
 
service class name ? : S2BasePluginService  <-Service名であるS2BasePluginServiceを入力してエンターキー
 
[ generate information ]
  module name             : sample_module
  service class name      : S2BasePluginService
  service test class name : S2BasePluginServiceTest
 
confirm ? (y/n) : y  <-yを入力してエンターキー
 
ここもS2Base1.0系とは少し動作が変わっていますね。
S2Base1.0系では、serviceコマンドを実行した場合interfaceとその実装、さらにdiconが作成されていたんですが、
S2Base2.0系では、実装だけが作成されるようになっています。
ちなみにServiceは、sampleプロジェクトのapp/modules/sample_module/serviceディレクトリに、
テストクラスは、sampleプロジェクトのtest/modules/sample_module/serviceディレクトリに出力されます。

ビジネスロジックを実装するServiceをinterfaceで定義していたのはDIする為ですが、
この部分をDIするメリットよりも定義(interface)と実装で2重管理が発生するデメリットの方が大きいかもです。
UnitTestを実行するとしても、プレーンなS2Baseのプロジェクトでは、
Serviceが一番上位の実装ということになります。
この部分のMockを使ってUnitTestをすることって、あんまり需要がなさそうです。
UIを作って、UIとServiceを別々に開発していくのであれば必要ですが、
その場合はInterfaceを定義して、実装の名前を変える(お約束な名前ならHogeServiceImple)とかに変えるだけですし。
S2ContainerからHogeServiceという名前でコンポーネントを取得する、
という規則でコードを書いているなら(普通に作っていくとそうなりますよね。)、
S2Containerから取得する際に使うHogeServiceがinterfaceなら、
デフォルトの動作である自動バインディングでS2Caontianerが実装クラスであるHogeServiceImpleを返してくれます。

Serviceを編集

先程のserviceコマンドで作成されたS2BasePluginServiceは、以下の様な内容になっています。

 
<?php
class S2BasePluginService {
    public function __construct(){}
}
 
何も実装されていないプレーンなクラスです。
ちなみにgoyaコマンドを使ってService、Dao、Entittyを一度に作成した場合は、
Daoを参照するフィールドとSetterメソッドが追加された形でServiceが作成されます。
で、S2BasePluginServiceはS2BasePluginDaoを参照するので、以下の様に編集してS2BasePluginDaoを参照できるようにします。
 
<?php
class S2BasePluginService {
    /** 
     * S2BasePluginDaoの実装です。
     * @var S2BasePluginDao S2BasePluginDao
     */
    private $dao;
 
    public function __construct(){}
 
    /** 
     * S2BasePluginDaoを設定します。
     * @param S2BasePluginDao $dao 設定するS2BasePluignDao
     */
    public functon setS2BasePluginDao(S2BasePluginDao $dao) {
        $this->dao = $dao;
    }
}
 
追加したフィールドとメソッドにはコメントが入っています。
で、この$daoをDIする為にdiconファイルを作り・・・ません。
S2ContainerApplicationContextを使う場合は、自動的にDIされます。

次に、Serviceのメソッドを実装してみましょう。

 
<?php
class S2BasePluginService {
    /** 
     * S2BasePluginDaoの実装です。
     * @var S2BasePluginDao S2BasePluginDao
     */
    private $dao;
 
    public function __construct(){}
 
    /** 
     * S2BasePluginDaoを設定します。
     * @param S2BasePluginDao $dao 設定するS2BasePluignDao
     */
    public function setS2BasePluginDao(S2BasePluginDao $dao) {
        $this->dao = $dao;
    }
 
    /** 
     * 全てのS2BasePluginを取得します。
     * @return S2Dao_ArrayList S2BasePluginを格納したS2Dao_ArrayList
     */
    public function getAllPlugins() {
        return $this->dao->findAllList();
    }
 
    /** 
     * 指定されたS2BasePluginを追加します。
     * @param S2BasePlugin $entity 追加するS2BasePlugin
     * @return int 更新された桁数
     */
    public function insertPlugin(S2BasePlugin $entity) {
        return $this->dao->insert($entity);
    }
 
    /** 
     * 指定されたS2BasePluginを更新します。
     * @param S2BasePlugin $entity 更新するS2BasePlugin
     * @return int 更新された桁数
     */
    public function updatePlugin(S2BasePlugin $entity) {
        return $this->dao->update($entity);
    }
 
    /** 
     * 指定されたS2BasePluginを削除します。
     * @param S2BasePlugin $entity 削除するS2BasePlugin
     * @return int 更新された桁数
     */
    public function deletePlugin(S2BasePlugin $entity) {
        return $this->dao->delete($entity);
    }
 
}
 
えーっと、S2BasePluginDaoをWrapしただけですね・・・^^;

UnitTestいたしましょう。

実装ができたならテスト。テスト駆動なら既に順序が逆っ!
気を取り直して、テストクラスにメソッドを実装していきましょう。
sampleプロジェクトのtest/modules/sample_module/serviceディレクトリのS2BasePluginServiceTest.phpです。

 
<?php
class S2BasePluginServiceTest extends PHPUnit_Framework_TestCase {
    private $module = 'sample_module';
    private $container;
    private $service;
 
    public function __construct($name) {
        parent::__construct($name);
    }
 
    public function testA(){
    }
 
    public function setUp(){
        print __CLASS__ . '::' . $this->getName() . PHP_EOL;
        $moduleDir = S2BASE_PHP5_ROOT . "/app/modules/{$this->module}";
        require_once($moduleDir . "/{$this->module}.inc.php");
        $this->container = S2ContainerApplicationContext::create();
        $this->service = $this->container->getComponent('S2BasePluginService');
    }
 
    public function tearDown() {
        print PHP_EOL;
        $this->container = null;
        $this->service = null;
    }
}
 
これを、(あくまでDelfino的に)テストしやすい様に編集。
PDOをフィールドにもって、SQLを直接発行できるようにしてみます。
それからsetUp()とtearDown()の中を変更してテスト実行後にロールバックするようにします。
 
<?php
class S2BasePluginServiceTest extends PHPUnit_Framework_TestCase {
    private $module = 'sample_module';
    private $container;
    private $service;
    /**
     * PDOへの参照
     * @var PDO
     */
    private $pdo;
 
    public function __construct($name) {
        parent::__construct($name);
    }
 
    public function setUp(){
        print __CLASS__ . '::' . $this->getName() . PHP_EOL;
        $moduleDir = S2BASE_PHP5_ROOT . "/app/modules/{$this->module}";
        require_once($moduleDir . "/{$this->module}.inc.php");
        $this->container = S2ContainerApplicationContext::create();
        $this->service = $this->container->getComponent('S2BasePluginService');
        $dataSource = $this->container->getComponent("dataSource");
        $this->pdo = $dataSource->getConnection();
        $this->pdo->beginTransaction();
    }
 
    public function tearDown() {
        print PHP_EOL;
        $this->pdo->rollBack();
        $this->pdo = null;
        $this->container = null;
        $this->service = null;
    }
}
 

じゃあ、まずはS2BasePluginService::getAllPlugins()

 
    public function testGetAllPlugins() {
        //Daoで全てのEntityを取得
        $daoResult = $this->service->getAllPlugins();
 
        //PDOで全てのカラムを取得
        $pdoResult = $this->pdo->query("select * from s2base_plugin");
        //レコード数でアサート
        $this->assertEquals($daoResult->size(), count($pdoResult->fetchAll()));
 
        //PreparedStatementを設定
        $pdoResult = $this->pdo->prepare("select * from s2base_plugin where id = ?");
 
 
        //ループしてアサート
        $iterator = $daoResult->iterator();
        while ($iterator->valid()) {
            $plugin = $iterator->current();
            $pdoResult->execute(array($plugin->getId()));
            $this->assertEquals($plugin->getName(), $pdoResult->fetchColumn(1));
            $iterator->next();
        }
    }
 
更新系はこんな感じで
 
    /**
     * S2BasePluginService::insertPlugin()のテスト
     */
    public function testInsertPlugin() {
        //念の為全レコード削除
        $this->pdo->exec("delete from s2base_plugin");
 
        //S2BasePluginを生成
        $plugin = new S2BasePlugin();
        $plugin->setName("TestPlugin");
 
        //Serviceでinsert
        $serviceResult = $this->service->insertPlugin($plugin);
        $this->assertEquals($serviceResult, 1);
 
        //PDOで取得
        $pdoResult = $this->pdo->query("select * from s2base_plugin where name = ?");
        $pdoResult->execute(array($plugin->getName()));
        $this->assertEquals($plugin->getName(), $pdoResult->fetchColumn(1));
    }
 
    /**
     * S2BasePluginService::updatePlugin()のテスト
     */
    public function testUpdatePlugin() {
        //Serviceで全てのEntityを取得
        $serviceResult = $this->service->getAllPlugins();
        $iterator = $serviceResult->iterator();
        //1件目のレコードを対象にします。
        $plugin = $iterator->current();
 
        //update
        $plugin->setName("TestPlugin");
        $this->service->updatePlugin($plugin);
 
        //PDOで取得
        $pdoResult = $this->pdo->query("select * from s2base_plugin where id = ?");
        $pdoResult->execute(array($plugin->getId()));
        $this->assertEquals($plugin->getName(), $pdoResult->fetchColumn(1));
    }
 
    /**
     * S2BasePluginService::deletePlugin()のテスト
     */
    public function testDeletePlugin() {
        //Serviceで全てのEntityを取得
        $serviceResult = $this->service->getAllPlugins();
        $iterator = $serviceResult->iterator();
        //1件目のレコードを対象にします。
        $plugin = $iterator->current();
 
        //delete
        $this->service->deletePlugin($plugin);
 
        //PDOで取得
        $pdoResult = $this->pdo->query("select * from s2base_plugin where id = ?");
        $pdoResult->execute(array($plugin->getId()));
        $this->assertEquals(count($pdoResult->fetchAll()), 0);
    }
 
ID指定で一意のEntityをとるメソッドがないので苦しいですね^^;
テストメソッドの中で問答無用でレコードの更新をしてますが、
S2BasePluginServiceTest::setUp()とS2BasePluginServiceTest::tearDown()の中で
トランザクションを使ってるからこんなことができます。
トランザクションを使わないまま、こんなメソッドを実行したら
大変なことになりますから、ご注意です。

テストの実行

後は作成したテストを実行します。
S2Base1.0系とはテストの実行方法が違うので注意です。

 
% s2bsae test S2BasePluginService
 
s2baseコマンドの第一引数にtestとつけて、第二引数にテスト対象を指定します。
実行されるテストの対象となる条件は、以下の通りです。
sampleプロジェクトのtestディレクトリ以下にあるもの。
で*Test*.phpというファイル名であるもの。
第二引数で指定されたテスト対象に、正規表現でマッチするもの
S2Base1.0系ではphingに-Dオプションを指定して、
td=(対象とするディレクトリ)もしくは、tt=(対象とするパターン)という風に
ユニットテストを実行していましたから、正規表現を使うことでひとつに統合されたわけですね。
とはいってもphingを使ったS2Base1.0系のテストを実行することもできますから
従来どおりの方法も使えます。

まとめ

S2Base2.0系からServiceの生成内容が変更になっています。
既存の資産の再利用には問題ないでしょうが、S2Base2.0系の実装の方が合理的に思えます。
それから、今更ですけどphingを使わなくてもS2Baseのコンソールが起動できるようになりました。
UnitTestの実行も同様です。UnitTestの実行はS2Base2.0系の方がシンプルにできてますね。
ということで、明日以降はInterceptorを実装したりとかロギングについてとか追っかけてまいります。

Category: PHP | Posted on: 2007/10/01 14:50 | Viewed: 1658

Comments

No comments yet

Add Comment

:

:
:

TrackBacks

このエントリにトラックバックはありません

TrackBack URL

http://www.azul.systems-noel.jp/trackback/item_71.html