share facebook facebook2 twitter menu hatena pocket slack

2014.08.12 TUE

PHPの魔法とAWSの配列

sebastian

WRITTEN BYsebastian

PHPでのaws-sdkのResponse

何てことは無いのですがコレ、配列で返ってくるんです。
PHPは確かに配列やjsonとは相性のいい言語なのですが如何せん全部配列で返ります。
PHPは元がText Parserなので当たり前と言えば当たり前な仕様ではあるのですがObject指向的な力学からすると
聊か、気が抜けてしまいます
まず、そもそも配列の要素キーはあるのか?を行うところから毎回実装していては話になりません
かと言って受け取った配列を全てカバーするラッパーを組んだところでAPIの仕様が変わればやり直しです
しかもamazonさんは結構頻繁に機能を追加したりして増えて行っているようです。

アクセスするときは全て、要素キーがあるか確認する様に書けばいいじゃないか?!
はい、確かにそうですね
ただ、美しくないです

僕の美意識に大きく反します

じゃあ、SDKアクセスをラップするクラスを書いて……

最初に戻ります。

じゃあ、どうすれば良いのだろうか?

仕方が無いのでProgrammerらしくProgramの魔法を使います

よくわかる現代魔法

別に魔法が使えるとか言う意味ではなくPHPには予め特定の動作を行う際に特定のメソッドが必ず呼び出されると言う機能があります
これは別にPHPに限らずですがClassからObjectをInstance化する際に必ず__constructが呼び出されるのもその一つです。
これらはMagic Methodなんて総称で呼ばれているので気が向いたら調べて見て下さい。
多分、PHPの使い方が一気に変わりますから
今回はそんな魔法の呪文を使って一風変わったAWS-SDKのアクセスを行ってみます

バカとテストと召喚獣

魔法の呪文と言ってもサモン!!とか言って何か呼び出す呪文じゃないです
でも召喚呪文です

PHPではClassのMethodを呼ぶ際に呼び出すMethodの有無を確認して対象となるMethodが見つからなければ呼ばれる__call__callStaticと言う呪文があります

何のこっちゃ?
と思われるでしょうから実際に書きます

class foo{
    function __call($name , $arguments) {
        echo "え?$nameってメソッド呼んだ???そんなメソッド俺知らねぇぞ??";
    }
}

$objFoo = new foo();
$objFoo->summon();

はい、じゃこれ実行してみましょう

え?summonってメソッド呼んだ???そんなメソッド俺知らねぇぞ??

と、こんな感じの結果になります
因みに__callの引数、$nameは実際に呼び出されたMethod名、$argumentsは呼び出し時の引数だ

これで何ができるの?
ただのエラー処理用でしょ?
とか思わないでね

この呪文の意味はまだ存在もしないMethodの呼び出しを動的に作れるって言う点にある。
つまり、居もしないMethodを召喚できる呪文な訳だ

まじしゃんず・あかでみぃ

じゃ、英霊・・・じゃなかったAWS召喚します。

こんな感じでClassを作っておいて

class InstanceInformation {
    private $_instance;
    function __construct(array $instance){
        $this->_instance = $instance;
    }

<pre><code>function __call($name , $arguments) {
    if ( array_key_exists($name , $this-_instance)){
        return $this-&amp;gt;instance[$name];
    } else {
        return null;
    }
}
</code></pre>

}

呼んでみる

$client = Ec2Client::factory();
$result = $client->describeInstance(array('InstanceIds'=>'i-xx223345'));

$instnace = $result['Reservations'][0]['Instancess'][0];

$objInstanceInformation = new InstanceInformation($instance);

echo $objInstanceInformation->InstanceId();

はい、実行結果

i-xx223345

順を追って説明していくと

  1. aws-sdkのEc2Client::describeInstancesでインスタンスの情報を取得
  2. 取りあえず複数の配列前提で返って来るので一個だけ取り出してみる
  3. 取り出したインスタンスの情報をInstanceInformationに入れてオブジェクトを作る
  4. オブジェクトに対してInstanceIdってメソッドを呼ぶ
  5. InstanceInformationInstanceIdなんてMethod知らないから__callの呪文が発動!!
  6. __call$nameに入った値[InstanceId]をオブジェクト化時に保持しておいた_instanceから探して来て返却する。

どうだ!面白いだろう?
え?別に配列に普通にアクセスすりゃ良いって?
いや、まぁ、その通りなんだけどさ

例えばな、ここをこうすると

class InstanceInformation {
    private $_instance;
    function __construct(array $instance){
        $this-&gt;_instance = $instance;
    }

<pre><code>function __call($name , $arguments) {
    if ( array_key_exists($name , $this-&amp;gt;_instance)){
        if ($name == 'Tags'){
            $Tags = $this-_instance['Tags'];
            $ResultTags = array();
            foreach($Tags as $tagValue){
                $key = $tagValue['Key'];
                $value = $tagValue['Value'];
                $ResultTags[$key] = $value;
            }
            return $ResultTags;
        }
        return $this-&amp;gt;instance[$name];
    } else {
        return null;
    }
}
</code></pre>

}

で、こいつを呼び出すだろ

$client = Ec2Client::factory();
$result = $client->describeInstance(array(
            'Filters' => array(
                array('Name' => 'Name' , 'Values' => array('Nyarlathotep'))
            )));
$instnace = $result['Reservations'][0]['Instancess'][0];
$objInstanceInformation = new InstanceInformation($instance);
$tags = $objInstanceInformation->Tags();
echo $tags['Name'];

実行結果はこうなる

Nyarlathotep

何やってるかと言うと
Tagsが呼ばれたらTagsの中のKeyとValueを使って連想配列作って返せってしてるだけ

Tagsの中身はこんな感じの構造になってる

array(
    'Tags' => array(
        array(
            'Name' => 'Name' ,
            'Value' => 'Nyarlathotep'
        ) ,
        array(
            'Name' => 'Group' ,
            'Value' => 'Cthulhu'
        ) ,
    )
)

配列何だけれどこれだと特定のTagが見たいって時にTagsをFor文で回してみなきゃならんのね
よって、一発で連想出来るようにただの置き換えたのがさっきのソースコード

何?毎回、Tagsが呼ばれるたびにFor文を回してたら同じだって?
じゃあ、こうやって__constructで書き換えときゃ問題ないだろ

class InstanceInformation {
    private $_instance;
    function __construct(array $instance){
        if ( array_key_exists($name , $instance)){
            $Tags = $instance['Tags'];
            $ResultTags = array();
            foreach($Tags as $tagValue){
                $key = $tagValue['Key'];
                $value = $tagValue['Value'];
                $ResultTags[$key] = $value;
            }
            $instance['Tags'] = $ResultTags;
        }
        $this-&gt;_instance = $instance;
    }

<pre><code>function __call($name , $arguments) {
    if ( array_key_exists($name , $this-_instance)){
        return $this-&amp;gt;instance[$name];
    } else {
        return null;
    }
}
</code></pre>

}

実はこのままだと実は宜しく無い事もある
PHPはオブジェクトだろうが配列だろうが全部、var_dumpすれば中身が見れると言う特性がある。
なんたって所詮はText Parserだからクラスオブジェクトって言ってもたかが知れてる

でだ、このInstanceInformastionをそのまま出力してみると

はっきり言って訳が分からない
何故かと言うとClassの構造情報まで保持してしまうからだ

じゃ、どうしようか?と言う事で出力する様にtoArrayを付けよう
つまり、配列に戻せるようにしましょって事だ

class InstanceInformation {
    private $_instance;
    function __construct(array $instance){
        if ( array_key_exists($name , $instance)){
            $Tags = $instance['Tags'];
            $ResultTags = array();
            foreach($Tags as $tagValue){
                $key = $tagValue['Key'];
                $value = $tagValue['Value'];
                $ResultTags[$key] = $value;
            }
            $instance['Tags'] = $ResultTags;
        }
        $this-&gt;_instance = $instance;
    }

<pre><code>function __call($name , $arguments) {
    if ( array_key_exists($name , $this-_instance)){
        return $this-&amp;gt;instance[$name];
    } else {
        return null;
    }
}

function toArray() {
    return $this-&amp;gt;_instance;
}
</code></pre>

}

はい、元々配列は中で保持してるんだからそのまま返せば良いだけだね
ここまで来るとじゃあEc2インスタンスが複数返ってくるときは?とか色々出てくるが今日はここまで

元記事は、
こちらです。

sebastian

sebastian