XOOPS Cube ServiceManager

　XOOPS Cube では、モジュールがクライアント・サーバ構成を取ってサービスを提供する手段が整っています。このモジュール間連携を利用する事でお互いに処理を委譲し欲しい結果だけを得るといったコーディングスタイルが可能です。

　このモジュール間連携の機能はサービスマネージャにより提供されています。サービスの提供を行うモジュールは、サービスマネージャに対してサービスの登録を行い、サービスを利用する側のモジュールはサービスマネージャに対して処理を委託します。

　実際の事例としては、コア・パッケージに同梱されているメッセージモジュールがあります。メッセージモジュールは、サービスマネージャを介してレガシーモジュールのユーザメニューブロックにメッセージURLや未読カウントを通知しています。クライアント側となるレガシーモジュールから見るとサービスマネージャに対しメッセージに関わるメソッドをコールするだけでユーザのインボックスのURLや未読カウントを取得しています。

　では、実際にどんな実装方法で動作しているかを解説します。

## 基本的な手順
+ サーバ側となるモジュールの Preload で ServiceManager に登録(`addService`)します。
+ 実際の処理を `XCube_Service` の継承クラスとしてモジュール配下の`service`フォルダに作成します。
+ `XCube_Service` 継承クラスでは `addFunction` を使い、サービスで提供する機能を記述します。
+ クライアント側は、プリロードで登録されたサービスを `getService` で呼び出し、 `createClient` にてハンドラを作成し、`$client->call('METHOD')` で関数を実行します。

## メッセージ・モジュールの解説
`XCube_Service` の使い方を学ぶ場合は、XOOPS Cube のコアに同梱されている message モジュールのコードが参考になります。まず、 preload を見てみましょう。

## ServiceManager への登録
ServiceManager への登録は、サービス提供側のモジュールの Prload フォルダにて行います。下のコードは message モジュールの preload です。`Message_Service` を呼び出して `addService` 関数により `privateMessage` という名前で `Message_Service` というクラスライブラリを登録しています。

```modules/message/preload/Preload.class.php
class Message_Preload extends XCube_ActionFilter
{
  public function postFilter()
  {
    require_once XOOPS_MODULE_PATH.'/message/service/Service.class.php';
    $service = new Message_Service();
    $service->prepare();      
    $this->mRoot->mServiceManager->addService('privateMessage', $service);
```

## サービスの作成
サービスの作成は、サービス提供側のモジュールの service フォルダにて行います。下のコードは、メッセージモジュールのサービスの抜粋です。前述のプリロードよりサービスマネージャへ登録されたコードの実態はこちらとなります。

```modules/message/service/Service.class.php
class Message_Service extends XCube_Service{
    public $mServiceName = 'Message_Service';
    public $mNameSpace = 'Message';
    public $mClassName = 'Message_Service';
    public function prepare(){
        $this->addFunction(S_PUBLIC_FUNC('string getPmInboxUrl(int uid)'));
    }
    public function getPmInboxUrl(){
        $root = XCube_Root::getSingleton();
        $uid = $root->mContext->mRequest->getRequest('uid');
        if ($uid > 0) {
            return XOOPS_URL.'/modules/message/index.php';
        }
        return "";
    }
}
```

## クライアント側

さて次にクライアントとなるモジュールから ServiceManager を使う方法を解説します。まず `getService` で preload に登録されたサービスを呼び出します。呼び出しが成功したら `createClient` でクライアントのハンドラを取得しハンドラに登録されているメソッドを呼び出します。以下のコードでは、 `getPmInboxUrl` 関数に対してユーザIDを指定してプライベートメッセージのURLを取得する処理を行っています。また、が取得出来た場合はさらに `getCountUnreadPM` 関数にて未読メッセージ数を取得しブロックの配列に入れています。こうして見覚えのある未読メッセージがブロック表示されているのです。

```modules/legacy/blocks/legacy_usermenu.php
$url = null;
$service =& $root->mServiceManager->getService('privateMessage');
if ($service != null) {
    $client =& $root->mServiceManager->createClient($service);
    $url = $client->call('getPmInboxUrl', array('uid' => $xoopsUser->get('uid')));

    if ($url != null) {
        $block['inbox_url'] = $url;
        $block['new_messages'] = $client->call('getCountUnreadPM', array('uid' => $xoopsUser->get('uid')));
        $block['flagShowInbox']=true;
    }
}
```

## サービスマネージャ実装方法

　では、実際にサービスマネージャを介する処理を組んでみます。事例としてクレジット決済モジュールで解説します。まず、以下のコードをご覧ください。これは、私がカートモジュールに組み込んだ当初のスタイルです。

```
    $ccard = new model_gethandler("payment","credit");
    $ccard->find($orderId);
    $status = $ccard->get('status');
    if($status==1){
....
```

　少し説明しますと、 `credit` というクレジット決済のモジュールの `payment` テーブルのハンドラクラスを生成して、注文IDで検索しステータス項目を取得しています。

　この処理で問題となるのは、組込実装時に、どのモジュールのどのテーブルにどのデータが格納されていて、そのデータの値がいくつだと実際に処理が成功したかをあらかじめ知っておかねばならない事です。

　決済方法が１つの場合はそれでも問題ないのですが、例えば別の決済モジュールやさらにその周辺処理に別モジュールを組み込もうとした場合、各モジュールのテーブル・データ・値を全て把握してその各モデルをハンドリングしなければなりません。例えば、決済管理、住所管理、受発注管理、カレンダー、配送センター指示等それぞれ別々に動いているモジュールの各種テーブルのステータスを調べて情報を表示するとなるとかなり煩雑になり後々のメンテナンスも面倒です。

　ではクライアント側となるモジュールがもっと手軽に処理するにはどうなればいいでしょうか？以下にサービスマネージャを使った実装例を記述します。

　まず、クライアント側はサービスマネージャで `creditPayment` というサービスを呼びだして  `checkOrderStatus` というメソッドの実行を依頼する形になります。ここには処理したいデータのモデル部分が何も無い事に注目ください。

``` clientProgram
$creditService = $root->mServiceManager->getService('creditPayment');
if ($creditService != null) {
    $client = $root->mServiceManager->createClient($creditService);
    $paid = $client->call('checkOrderStatus', array('orderID' => $orderId));
}
```

　クライアント側の呼び出しが出来たらそれに見合うサービスをサーバ側で用意します。サーバ側ではプリロードにサービスを登録 `addService` し、service フォルダにサービスを提供するコントローラを記述します。

``` modules/credit/preload/Preload.class.php
class Credit_Preload extends XCube_ActionFilter
{
    public function postFilter()
    {
        require_once XOOPS_MODULE_PATH . '/credit/service/Service.class.php';
        $service = new Credit_Service();
        $service->prepare();

        $this->mRoot->mServiceManager->addService('creditPayment', $service);
    }
}
```

　そして、提供するサービスの実態は service フォルダに記載します。 `prepare` には関数のI/Oを文字列で記述します。また呼び出される関数もこちらに用意します。サービスの実態は、MVCでいう所のコントローラに位置しますので、このサービス提供用のコントローラはclass配下のモデルを使用してデータを処理する事になります。

``` modules/credit/service/Service.class.php
class Credit_Service extends XCube_Service
{
    public $mServiceName = 'Credit_Service';
    public $mNameSpace = 'Credit';
    public $mClassName = 'Credit_Service';

    public function prepare()
    {
        $this->addFunction(S_PUBLIC_FUNC('int checkOrderStatus(int orderId)'));
    }
    public function checkOrderStatus()
    {
        $ret = false;
        $root = XCube_Root::getSingleton();
        $orderId = $root->mContext->mRequest->getRequest('orderId');
        if ($root->mContext->mUser->isInRole('Site.RegisteredUser')) {
            $uid = $root->mContext->mXoopsUser->get('uid');
            $modHand = xoops_getmodulehandler('payment', 'Credit');
            $myrow = $modHand->getDataById($uid, $orderId);
            if ($myrow) {
                if ($myrow['status'] == 1) $ret = true;
            }
        }
        return $ret;
    }
}
```

　この様にする事でサービスマネージャに注文番号で問い合わせ結果が返ります。モデルの構造を知らなくても $paid という変数にその注文は支払済か結果を返してくれます。行数的にはそんなに変わらないと思うでしょうが、モジュール間が粗結合になりメンテナンス性が高まりました。サービス提供側のモジュールがいくら構成を変えても `checkOrderStatus` にステータスを返せば、サービス利用側のモジュールはいっさいコードをタッチせずにメンテナンスできます。

　如何でしょうか？この様なモジュール間通信を備えたCMSはまだまだ少ないと思います。XOOPS Cube は様々なモジュールを効率的に組み合わせる事でさらに発展させる事が可能です。