LaravelのファイルストレージでConohaのオブジェクトストレージを使う

[Laravelのファイルストレージ](https://readouble.com/laravel/5.4/ja/filesystem.html)、便利ですね。
s3、FTPなど様々なファイルシステムを同じインターフェースで扱うことができます。

元からローカル、[Amazon S3](https://aws.amazon.com/jp/s3/)、[rackspace](https://www.rackspace.com/)などのドライバが使えるようになってますが、
S3は料金体系が難しい、rackspaceはよくわからない、ということで、
今回は[Conohaのオブジェクトストレージ](https://www.conoha.jp/objectstorage/)に接続してみました。

# 作業

## ライブラリのインストール

前述のrackspaceが、conohaと同じ[openstack](https://thinkit.co.jp/story/2014/06/10/4999)という規格でできているので、
conohaでもこのライブラリを使います。
用意されているとはいえライブラリの追加インストールが必要みたいなのでcomposerでインストール。
参考：https://readouble.com/laravel/5.4/ja/filesystem.html#driver-prerequisites

```
composer require league/flysystem-rackspace
```

## ServiceProviderの登録

そのまま使ってもうまく接続できませんので、認証部分だけオリジナルで作成します。
以下のServiceProviderを新規作成します。

```php:app/Providers/ConohaServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use League\Flysystem\Filesystem;
use League\Flysystem\Rackspace\RackspaceAdapter;
use OpenCloud\OpenStack;

class ConohaServiceProvider extends ServiceProvider
{
    /**
     * サービスの初期処理登録後に実行
     *
     * @return void
     */
    public function boot()
    {
        $filesystem = $this->app->make('filesystem');
        $filesystem->extend('conoha', function($app, $config) {
            $client = new OpenStack($config['auth_url'], [
                'username' => $config['username'],
                'password' => $config['password'],
                'tenantName' => $config['tenant_name'],
            ]);
            $client->authenticate();
            $service = $client->objectStoreService('Object Storage Service', $config['region']);
            $container = $service->getContainer($config['container']);

            return new Filesystem(new RackspaceAdapter($container));
        });
    }

    /**
     * コンテナで結合の登録
     *
     * @return void
     */
    public function register()
    {
        //
    }
}
```

ServiceProviderを作成したら、`config/app.php`に登録します。

```php:config/app.php
   'providers' => [
        // ...

        \App\Providers\ConohaServiceProvider::class, // ←追加
    ],
```

## configの登録

認証情報などを`config/filesystem.php`と`.env`に追加します。

```php:config/filesystem.php
   'disks' => [
        // ...

        'conoha' => [
            'driver'        => 'conoha',
            'auth_url'      => env('OS_AUTH_URL', ''),
            'username'      => env('OS_USERNAME', ''),
            'password'      => env('OS_PASSWORD', ''),
            'tenant_name'   => env('OS_TENANT_NAME', ''),
            'container'     => env('OS_CONTAINER', ''),
            'region'        => env('OS_REGION', ''),
        ],
    ],
```

```:.env
OS_AUTH_URL=
OS_USERNAME=
OS_PASSWORD=
OS_TENANT_NAME=
OS_CONTAINER=
OS_REGION=
```

各設定値の意味は以下のとおりです。
※ConohaのAPIに関しては公式ドキュメントが参考になります。
事前に、cyberduckなりでコンテナを作っておくと良いです。
https://www.conoha.jp/guide/objectstoragecyberduck.php

| 名前 | 説明 |
|:--|:--|
|**OS_AUTH_URL**|Conohaのコントロールパネル→API→エンドポイント→Identity ServiceのURL|
|**OS_USERNAME**|APIユーザー名|
|**OS_PASSWORD**|APIユーザーパスワード|
|**OS_TENANT_NAME**|Conohaのコントロールパネル→API→テナント情報→テナント名|
|**OS_CONTAINER**|作成したコンテナ名|
|**OS_REGION**|リージョン名（エンドポイントの`「tyo1」.conoha.io`の部分。東京ならtyo1）|

# 使う

ここまでやれば、他のドライバと同じように使えます。
Laravelのファイルストレージについては公式ドキュメントが参考になります。
https://readouble.com/laravel/5.4/ja/filesystem.html

たとえばPOSTされたファイルをアップロードするなら以下のようになります。

```php
$path = $request->file('avatar')->store('avatars', 'conoha');
```

# ServiceProvider解説

```php:app/Providers/ConohaServiceProvider.php
        $filesystem = $this->app->make('filesystem');
        $filesystem->extend('conoha', function($app, $config) {
```

filesystemのドライバに新しく`conoha`を追加します。`extend`メソッドの第１引数がドライバ名です。
functionの引数`$config`には、`config/filesystem.php`で定義した`username`などが入ってます。

```php:app/Providers/ConohaServiceProvider.php
            $client = new OpenStack($config['auth_url'], [
                'username' => $config['username'],
                'password' => $config['password'],
                'tenantName' => $config['tenant_name'],
            ]);
```

`OpenCloud\OpenStack`のインスタンスを生成します。

認証情報はconfigで設定した値を渡しているだけです。
※rackspaceのドライバではこの部分がrackspace専用の実装になっており、
そのまま使うとconohaの認証と合わずにエラーになってしまってました。

```php:app/Providers/ConohaServiceProvider.php
            $client->authenticate();
```

認証を行います。
※ここで認証しないと、このあとの`objectStoreService`のところで、カテゴリが見つからない的なエラーになります。

```php:app/Providers/ConohaServiceProvider.php
            $service = $client->objectStoreService('Object Storage Service', $config['region']);
            $container = $service->getContainer($config['container']);
```

オブジェクトストレージにアクセスするサービスを取得し、設定ファイルで指定したコンテナの情報を取り出します。

```php:app/Providers/ConohaServiceProvider.php
            return new Filesystem(new RackspaceAdapter($container));
```

最後に、`RackspaceAdapter`に`$container`を渡し、`Filesystem`を生成して返します。
認証部分以外に関しては、rackspaceがそのまま使えるみたいですね。

# (追記)WebMode時のURLを取得したい場合

ConohaのオブジェクトストレージをWebModeに設定しており、そのURLを取得したい場合、
上記の方法だと`url()`メソッドが「対応してない」とエラーになってしまうことがわかったので、
対応方法を追記します。

なお、WebModeの詳しい説明はこちらをご参照ください。
https://www.conoha.jp/guide/objectstoragesdk.php

## configに設定追加

WebMode時のURLのベースパスを登録します。

```:.env
OS_WEB_URL=[WebMode時のURLベース]
```

```php:app/filesystems.php
        'conoha' => [
            // ...
            'web_url'       => env('OS_WEB_URL', ''), // ←追加
            // ...
        ],
```

## `ConohaAdapter`を新たに作成

rackspaceのアダプタを継承してConoha用のアダプタを登録します。
なお、作成する場所は以下じゃなくても大丈夫だと思います。

```php:app/Services/Filesystem/ConohaAdapter.php
<?php

namespace App\Services\Filesystem;

use League\Flysystem\Rackspace\RackspaceAdapter;

class ConohaAdapter extends RackspaceAdapter
{
    /**
     * WEB公開用のURLを取得
     * @param $path
     * @return string
     */
    public function getUrl($path)
    {
        return config('filesystems.disks.conoha.web_url') . DIRECTORY_SEPARATOR . $path;
    }
}
```

## `ConohaServiceProvider`でインスタンス化するアダプタを`ConohaAdapter`に変更

```php:app/Providers/ConohaServiceProvider.php
            return new Filesystem(new ConohaAdapter($container));
```

`RackspaceAdapter`を継承しているので他の機能はそのまま使えます。

---

これで、`url($path)`メソッドでWebMode時のURLが取得できます。