Amazon CloudSearch の domain を AWS SDK for PHP で作成する

## ゴール
Cloud formation は CloudSearchをサポートしていなさそうなので、AWSのコンソールから行える CloudSearch のドメイン作成と同等のことを AWS SDK for PHP で実現します。

## 環境
### 実行環境
* AWS SDK for php version 2.8.31
* php 7.0.21
* PHPはEC2で実行しました。EC2には ` CloudSearchFullAccess` を付与したIAMロールが設定してあります。

## composer.json
```json:composer.json
    "require": {
        "aws/aws-sdk-php": "2.*"
    }
```

## CloudSeachのドメインを作成する PHPスクリプト
繰り返して実行すると、設定が更新されます。「既に存在するのでダメ」なエラーは発生しませんでした。

```php
<?php

require_once 'vendor/autoload.php';

use Aws\CloudSearch\CloudSearchClient;
use Aws\Common\Enum\Region;


const DOMAIN_NAME = '[作成するドメイン名]';

$client = CloudSearchClient::factory([
    'profile' => 'cloudsearch',
    'region'  => Region::US_WEST_2  // Oregon
]);

// ドメイン作成
$doamin = $client->createDomain(['DomainName'=>DOMAIN_NAME]);
// var_dump($doamin);

// Scaling Options でインスタンスサイズを設定
$client->UpdateScalingParameters([
    'DomainName'=>DOMAIN_NAME,
    'ScalingParameters' => [
        'DesiredInstanceType' => 'search.m1.small',
        'DesiredReplicationCount' => 1,
        // 'DesiredPartitionCount' => 1, // only search.m3.2xlarge
    ]
]);

// Availability Options でマルチAZ を無効 に設定
$client->UpdateAvailabilityOptions([
    'DomainName'=>DOMAIN_NAME,
    'MultiAZ' => false
]);

// Access Policies で指定したIPアドレスからのアクセスを許可
$client->UpdateServiceAccessPolicies([
    'DomainName'=>DOMAIN_NAME,
    'AccessPolicies' => accessPolicy(),
]);

// フィールドの設定
foreach( defineFields() as $name => $f )
{
    $f['IndexFieldName'] = $name;
    $client->defineIndexField([
        'DomainName' => DOMAIN_NAME,
        'IndexField' => $f,
    ]);
}

$indexFiled = $client->indexDocuments(['DomainName' => DOMAIN_NAME]);
// var_dump(indexFiled);

/**
 * アクセス ポリシー
 */ 
function accessPolicy()
{
    $policy = <<<EOM
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": "cloudsearch:*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "[接続を許可するIPアドレス]"
        }
      }
    }
  ]
}
EOM;
    return $policy;
}

/**
 * フィールドの定義
 */
function defineFields()
{
    // AnalysisScheme
    // http://docs.aws.amazon.com/ja_jp/cloudsearch/latest/developerguide/text-processing.html
    return [
        'tweet_id' => [
            'IndexFieldType'=> 'literal',
            'LiteralOptions' => [
                'SearchEnabled' => true,
                'FacetEnabled' => false,
                'ReturnEnabled' => true,
                'SortEnabled' => true,
            ],
         ],
         'tweet_url' => [
            'IndexFieldType'=> 'text',
            'TextOptions' => [
                'AnalysisScheme' => '_en_default_',
                'ReturnEnabled' => true,
                'SortEnabled' => false,
                'HighlightEnabled' => false,
            ],
         ],
         'user_id' => [
            'IndexFieldType'=> 'text',
            'TextOptions' => [
                'SearchEnabled' => true,
                'FacetEnabled' => false,
                'ReturnEnabled' => true,
                'SortEnabled' => true,
            ],
         ],
         'user_screen_name' => [
            'IndexFieldType'=> 'text',
            'TextOptions' => [
                'AnalysisScheme' => '_en_default_',
                'ReturnEnabled' => true,
                'SortEnabled' => true,
                'HighlightEnabled' => false,
            ],
         ],
         'created_at' => [
            'IndexFieldType'=> 'date',
            'DateOptions' => [
                'FacetEnabled' => true,
                'ReturnEnabled' => true,
                'SearchEnabled' => true,
                'SortEnabled' => true,
            ],
         ],
         'text' =>[
            'IndexFieldType'=> 'text',
            'TextOptions' => [
                'AnalysisScheme' => '_ja_default_',
                'ReturnEnabled' => true,
                'SortEnabled' => true,
                'HighlightEnabled' => true,
            ],
         ],
         'retweet_count' => [
            'IndexFieldType'=> 'int',
            'IntOptions' => [
                'SearchEnabled' => true,
                'FacetEnabled' => true ,
                'ReturnEnabled' => true,
                'SortEnabled' => true,
            ],
         ],
         'retweeted' => [
            'IndexFieldType'=> 'literal',
            'LiteralOptions' => [
                'SearchEnabled' => true,
                'FacetEnabled' => false,
                'ReturnEnabled' => true,
                'SortEnabled' => true,
            ],
         ],
         'hash_tags' => [
            'IndexFieldType'=> 'text-array',
            'TextArrayOptions' => [
                'SearchEnabled' => true,
                'AnalysisScheme' => '_ja_default_',
                'HighlightEnabled' => true,
                'ReturnEnabled' => true,
            ],
         ],
         "hash_tags_facet" => [
            'IndexFieldType'=> 'literal-array',
            'LiteralArrayOptions' => [
                'FacetEnabled' => true,
                'ReturnEnabled' => false,
                'SourceFields' => 'hash_tags',
            ],
         ],
         'urls' => [
            'IndexFieldType'=> 'text-array',
            'TextArrayOptions' => [
                'SearchEnabled' => true,
                'AnalysisScheme' => '_ja_default_',
                'HighlightEnabled' => true,
                'ReturnEnabled' => true,
            ],
         ],
    ];
}
```

実行後の `Indexing Options` の画面キャプチャ
![スクリーンショット 2017-11-12 11.53.27.png](https://qiita-image-store.s3.amazonaws.com/0/145742/e38e1284-d0c4-883c-48c1-cdcb2cac4b25.png)

TwitterのAPIでツイートを取得して、主に ツイートのメッセージとハッシュタグで検索します。


## メモ
### インスタンス・タイプ

今回はデータ量が少ないので、明示的に `search.m1.small` にしています。

インスタンス・タイプを決めるにあたり、参考になる情報がドキュメントにあります。
> 一括アップロードを実行するには、以下に従います。
>
> ・バッチのサイズを制限の 5 MB にできるだけ近付けてください。小さなバッチを大量にアップロードすると、アップロードとインデックス作成の処理速度が低下します。
> ・必要なインスタンスタイプを、デフォルトの search.m1.small より大きなインスタンスタイプに設定します。使用できるアップロードスレッドの数は、ドメインで使っている検索インスタンスのタイプ、データの性質、インデックス作成オプションによって異なります。インスタンスタイプが大きいほど、アップロード容量が大きくなります。search.m1.small インスタンスにバッチを並列アップロードしようとすると、通常は、高い確率で 504 または 507 エラーが発生します。必要なインスタンスタイプの設定の詳細については、「スケーリングオプションの設定」を参照してください。

〜 省略 〜

> データが 1 GB 未満のデータセットまたは 1,000,001 KB 未満のドキュメントの場合は、スモール検索インスタンスで十分です。1～8 GB のデータセットをアップロードするには、アップロードする前に、必要なインスタンスタイプを search.m3.large に設定することをお勧めします。8～16 GB のデータセットは、search.m3.xlarge で始まります。16～32 GB のデータセットは、search.m3.2xlarge で始まります。アップロードのサイズが 32 GB を超える場合は、インスタンスタイプに search.m3.2xlarge を選択し、データセットに対応できるように、望ましいパーティション数を増やします。各パーティションに最大 32 GB のデータを格納できます。さらに多くのアップロード容量が必要な場合、または 500 GB を超えるインデックスを作成する場合は、Service Increase Limit Request を送信します。



http://docs.aws.amazon.com/ja_jp/cloudsearch/latest/developerguide/uploading-data.html#bulk-uploads

### TextField の AnalysisScheme
管理コンソールから操作する時にプルダウンで表示される言語別のテキスト処理の一覧です。
http://docs.aws.amazon.com/ja_jp/cloudsearch/latest/developerguide/text-processing.html

目的の言語の デフォルトの分析スキーム を `AnalysisScheme` に指定します。日本語は `_ja_default_` 、英語は`_en_default_` です。

> 日本語（ja）
アルゴリズム語幹解釈オプション: full
アルゴリズム複混合が有効
オプションのトークン分割ディクショナリ
デフォルトの分析スキーム: \_\_ja_default\_\_
・アルゴリズム語幹解釈: full
・デフォルトのストップワードディクショナリ