機能拡張を可変クラスで局所化する

# １．はじめに
　うちではgoogleAssistantからIFTTT経由でメインのＰＣをＷＯＬできるのですが、ここのところうまくうごきません。googleAssistantと外部サービスはあんまりうまく行かないときがあるので今回もそうなのかもしれません（調べていない）。ただ、IFTTTをどうこうするというより、googleAssitantから自前のフルフィルメントを通してＷＯＬする経路を造ってもいいのかもしれません。

# ２．おさらい
　自前で用意している〈試験版〉フルフィルメントからジュークボックスサービスに連携するモデル図を以下に示します。
![googleAssistantButlerSystem.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2696131/fd9f35ae-b4e1-1057-b9a6-06f0e62d3cca.png)

　フルフィルメントはgoogleAssistantから送られる登録済みのデバイスが使える機能要求を受け付けます。単純なものではスマートコンセントのオン／オフなどです。
　ジュークボックスを造る前に試験的なデバイスとして疑似的なスマートコンセントを登録したままにしてあるのでこれをＷＯＬのトリガとして転用することにします。
## ２．１．ＷＯＬをどこに作るか
　問題は機能拡張のやり方です。きちんと動いているところにはあまり手を入れたくない。理想を言えば追加機能のコードは既存のコードから独立していて、仮におかしくなるとしても新規追加したところだけに閉じて欲しい。ただ、そうは言っても新しい機能は何らかの形で呼び出されなければならないのでどこかで既存のロジックに入り込まざるを得ません。
　安直にすでにある`jukebox server`の中にＷＯＬ機能を取り込んでしまうという案はあります。ただ、さすがにそれは作りとしてどうでしょうという感じがします。googleAssistantから呼び出されるスマートデバイスとしてジュークボックスと（疑似）スマートコンセントは別々のデバイスなので相乗りは避けたい。今後の機能拡張でも同じことをやるのか、という話です。

### ２．１．１．プラグイン化する
　一案として、フルフィルメント(`smart device butler`)の外部に置いてあるソースコードをプラグイン的に自動的に組み込んでくれたりしないだろうか、ということを思いました。試験的なモックアップを作成する過程でジュークボックスを呼び出すコードが`smart device butler`には既に入り込んでいるのですが、その作りのまま拡張すると個々のデバイスＩＤとデバイスが持つ機能に依存した作り込みを増やしていくことになります。そのロジックを外部化することができたら保守性は良さそうです。ＰＨＰには外部から動的にソースコードを読み込む、クラスライブラリのオートローディングがあるので、これが使えそうな気がします。
## ２．２．可変クラスのサンプルコード
　ＰＨＰにはいろいろ融通無碍なところがあるので、マニュアルに記載はありませんが、可変変数やら可変関数やら使えるので、可変クラスも使えるのではないかという気がします。
　次のようなサンプルを書いて、動かしてみました。
```
<?php
class foo{
	function __construct(){
		echo "foo class\n";
	}
}

$class="foo";
$a = new $class();
?>
```
 できました。可変クラスの実体をオートローディングで動的に読み込ませます。
# ３．ビフォー＆アフター
　もともとは以下のようなロジックです。ロジックも何もない感じですが。`ts01`がスマートコンセント、`ss01`がジュークボックスデバイスです。
```
  $m = count($arrId);
  for($i=0;$i<$m;$i++){
    if($arrId[$i]->id == "ts01"){
      if($arrCmd[$i]->command == "action.devices.commands.OnOff"){
        if($arrCmd[$i]->params->on){
          $obj = new wake("18:31:BF:XX:XX:XX");
        }
      }    
    }
    if($arrId[$i]->id == "ss01"){
      $objGpd = new gpdComm(GPD_MCLIENT);
      if($arrCmd[$i]->command == "action.devices.commands.mediaResume"){
        $objGpd->sendOrder("PLAY");
      }
      if($arrCmd[$i]->command == "action.devices.commands.OnOff"){
        if($arrCmd[$i]->params->on){
          $objGpd->sendOrder("PLAY");
        } else {
          $objGpd->sendOrder("STOP");
        }
      }
    }
  }
```
　idを拾って、それぞれのデバイスが持つ機能に合わせてそれぞれの処理を行うロジックがフルフィルメント側に入っていましたが、これを可変クラスのインスタンス生成に置き換えます。`$arrCmd`と`$arrId`の扱いはもうちょっとどうにかならんのかという感じですが、今はこのくらいにしといてやります。
　これを可変クラスを使って書き換えます。
　クラスの実体が無い場合や、読み込むクラス定義ファイルのチェックなど安全装置が必要と思いますが、だいたいこんな感じになりました。
```
  $m = count($arrId);
  for($i=0;$i<$m;$i++){
    $objCls =$arrId[$i]->id;
    $obj = new $objCls($arrCmd[$i]);
  }
```
　if構文の中身が可変クラスとして外部化された結果、フルフィルメント内部では単にクラスオブジェクトを生成するだけの動きとなり、かつ呼び出すクラスは不定となりました。実際の動きはデバイスごとのクラス定義で決定されるわけです。例えばＷＯＬであれば次のようなものになります。wakeクラスはさらに外部にあるＷＯＬ処理を行うクラスです。
```
<?php
require("classloader.php");
class ts00{
  public function __construct($cmd){
    if($cmd->command == "action.devices.commands.OnOff"){
      if($cmd->params->on){
        $obj = new wake("18:31:BF:XX:XX:XX");
      }
    }
  }
}
?>
```
# ４．おわりに
　プラグイン化することでgoogleAssistantとのOauth2処理やフルフィルメントの受付処理をするロジックと、（疑似的な）スマートデバイス応答を行うロジックのソースファイルを分離することができました。デバイス増やすたびにロジックに手を入れる手間がなくなるので、扱いやすくなった感じがします。