静的なクライアントファサード

はじめに

ニフティクラウド SDK for PHPの第2.4版では、静的なクライアントファサードを有効にし、利用できるようになりました。 「ファサード」とは、サービスビルダで利用可能な、サービスクライアント向けの静的で使いやすいインタフェイスのことです。 例えば通常のクライアントインスタンスであれば、次のようなコードを記述することになるでしょう。

// Get the configured S3 client from the service builder
$s3 = $aws->get('s3');

// Execute the CreateBucket command using the S3 client
$s3->createBucket(array('Bucket' => 'your-new-bucket-name'));

クライアントファサードを有効にすれば、次のコードで同じ処理を実装できます。

// Execute the CreateBucket command using the S3 client
S3::createBucket(array('Bucket' => 'your-new-bucket-name'));

なぜクライアントファサードを使うのか?

静的なクライアントファサードを利用するか否かはまったく自由です。 この機能をSDKに組み込んだのは、PHP開発者の中でも、静的な記述方法を好む人や、 Code Ignitor、Laravel、Kohanaなど、静的なメソッド起動方式を広く採り入れた PHPフレームワークに慣れている人を想定してのことです。

静的なクライアントファサードを利用しても、クライアントインスタンスを使う方法に比べてそれほど実利はありません。 しかし、コードが簡潔になり、クライアントオブジェクトが必要な文脈に、 サービスビルダやクライアントインスタンスが入ってしまうのを回避できます。 したがってコードが書きやすく、また、読んで理解しやすくなるのです。 クライアントファサードを導入するか否かは、完全に好みの問題です。

ニフティクラウド SDK for PHPにおいてクライアントファサードが働く方式は、 Laravel 4 Frameworkにおいてファサードが機能する方式 に似ています。 メソッド呼び出しは、静的クラスを呼び出しているように見えても、 (サービスビルダ側に保持されている) 実際のクライアントインスタンスに対する メソッド呼び出しに置き換わります。 したがって、クライアントファサードを介してクライアントを使うようにしておけば、 ユニットテストの際、仮の実装で模擬することができます。 すなわち、オブジェクト指向プログラミングで静的なクラスを用いる場合に、一般に生じる問題のひとつが、この場合には起こらないのです。 クライアントファサードを使ったコードのテスト方法については、 後述の「 クライアントファサードを使っているコードのテスト 」を参照してください。

クライアントファサードの有効化および使い方

静的なクライアントファサードを有効にし、アプリケーションで利用するためには、 サービスビルダのセットアップ時に、 Aws\Common\Aws::enableFacades メソッドを実行しなければなりません。

// Include the Composer autoloader
require 'vendor/autoload.php';

// Instantiate the SDK service builder with my config and enable facades
$aws = Aws::factory('/path/to/my_config.php')->enableFacades();

このコードでは、クライアントファサードをセットアップし、グローバルな名前空間から「別名で参照できる」ようにしています。 以降はどこからでも、簡潔で表現力豊かな書き方で、AWSサービスとやり取りするコードを実装できることになります。

// List current buckets
echo "Current Buckets:\n";
foreach (S3::getListBucketsIterator() as $bucket) {
    echo "{$bucket['Name']}\n";
}

$args = array('Bucket' => 'your-new-bucket-name');
$file = '/path/to/the/file/to/upload.jpg';

// Create a new bucket and wait until it is available for uploads
S3::createBucket($args) and S3::waitUntilBucketExists($args);
echo "\nCreated a new bucket: {$args['Bucket']}.\n";

// Upload a file to the new bucket
$result = S3::putObject($args + array(
    'Key'  => basename($file),
    'Body' => fopen($file, 'r'),
));
echo "\nCreated a new object: {$result['ObjectURL']}\n";

また、グローバルでない名前空間に、ファサードをマウントすることも可能です。 例えば「Services」名前空間でのみ参照できるようにしたい場合、次のようになります。

Aws::factory('/path/to/my_config.php')->enableFacades('Services');

$result = Services\DynamoDb::listTables();

どのクライアントファサードが利用できるかは、サービスビルダの設定 ( 設定 を参照) によって決まります。 SDKのデフォルトの設定ファイルを使って (extends) いるか、何も設定をしていなければ、 サービスビルダのインスタンスや (有効になった) クライアントファサードから、 あらゆるクライアントが自動的にアクセス可能になります。

デフォルトの設定ファイル (src/Aws/Common/Resources/aws-config.php) には 次のような記述があります。

's3' => array(
    'alias'   => 'S3',
    'extends' => 'default_settings',
    'class'   => 'Aws\S3\S3Client'
),

'class' キーは、静的なクライアントファサードが代理するクライアントクラスを表します。 'alias' キーはクライアントファサードの名前 (別名) です。 サービスビルダの設定で、 'alias' キー、 'class' キーの両方を指定したものだけが、 静的なクライアントファサードとしてマウントされます。 サービスビルダの設定を更新または追加すれば、独自のクライアントファサードに改変し、あるいは新規に作成することも可能です。

クライアントファサードを使っているコードのテスト

SDKに組み込みの静的クライアントファサードは、単に静的クラスと呼んでいますが、 メソッド呼び出しがすべて、(サービスビルダ側に保持されている) 実際のクライアントインスタンスに対する メソッド呼び出しに置き換わります。 したがって、ユニットテストの際、仮の実装で模擬することができます。 すなわち、オブジェクト指向プログラミングで静的なクラスを用いる場合に、一般に生じる問題のひとつが、この場合には起こらないのです。

テストの際、クライアントファサードを仮の実装で模擬するためには、 サービスビルダの所定のキー (通常はクライアントファサードが実際に参照するクライアントを記述するキー) に、 模擬するクライアントオブジェクトを明示的に設定してください。 PHPUnitテストで実際に模擬を行っている完全な例を示します (これは実働コードではなく、例として使うために作成したものです)。

<?php

use Aws\Common\Aws;
use Guzzle\Service\Resource\Model;
use YourApp\Things\FileBrowser;

class SomeKindOfFileBrowserTest extends PHPUnit_Framework_TestCase
{
    private $serviceBuilder;

    public function setUp()
    {
        $this->serviceBuilder = Aws::factory();
        $this->serviceBuilder->enableFacades();
    }

    public function testCanDoSomethingWithYourAppsFileBrowserClass()
    {
        // Mock the ListBuckets method of S3 client
        $mockS3Client = $this->getMockBuilder('Aws\S3\S3Client')
            ->disableOriginalConstructor()
            ->getMock();
        $mockS3Client->expects($this->any())
            ->method('listBuckets')
            ->will($this->returnValue(new Model(array(
                'Buckets' => array(
                    array('Name' => 'foo'),
                    array('Name' => 'bar'),
                    array('Name' => 'baz')
                )
            ))));
        $this->serviceBuilder->set('s3', $mockS3Client);

        // Test the FileBrowser object that uses the S3 client facade internally
        $fileBrowser = new FileBrowser();
        $partitions = $fileBrowser->getPartitions();
        $this->assertEquals(array('foo', 'bar', 'baz'), $partitions);
    }
}

あるいは、クライアントからの応答を模擬するだけでよければ、 Guzzle Mock Plugin を用いる方法もあります。