PHP を使用して Azure Blob Storage へアップロード


# 環境
- Windows10 Pro 64bit
- PHP 7.4.4
- Composer 2.1.5

ローカル環境にて PHP を使用した Azure Blob Storage へのアップロード検証。
以降、Cドライブ直下に `tutorial-azure-blob-storage` というフォルダを作成し、フォルダ内にて作業を実施。

# 事前準備

- Microsoft Azure Portal にてリソースグループ及びストレージアカウントの作成
 - ホーム > ストレージアカウント名 > コンテナー
     - コンテナーの作成（今回は `tutorial-container` を作成）
 - ホーム > ストレージアカウント名 > アクセスキー
     - 接続文字列をメモ

# microsoft/azure-storage-blob インストール
https://github.com/Azure/azure-storage-blob-php
`microsoft/azure-storage-blob` をインストールする。

```terminal
C:\tutorial-azure-blob-storage>composer require microsoft/azure-storage-blob
```

2021/09/03 時点では以下のバージョンがインストールされた。

```terminal
C:\tutorial-azure-blob-storage>composer show
guzzlehttp/guzzle              7.3.0 Guzzle is a PHP HTTP client library
guzzlehttp/promises            1.4.1 Guzzle promises library
guzzlehttp/psr7                2.0.0 PSR-7 message implementation that also provides common utility methods
microsoft/azure-storage-blob   1.5.2 This project provides a set of PHP client libraries that make it easy to acce...
microsoft/azure-storage-common 1.5.1 This project provides a set of common code shared by Azure Storage Blob, Tabl...
psr/http-client                1.0.1 Common interface for HTTP clients
psr/http-factory               1.0.1 Common interfaces for PSR-7 HTTP message factories
psr/http-message               1.0.1 Common interface for HTTP messages
ralouphie/getallheaders        3.0.3 A polyfill for getallheaders.
```

# ディレクトリ構造
```
.
├── vendor
├── composer.json
├── composer.lock
├── upload.php
└── dummy.png
```

# アップロード
```php:upload.php
<?php
require_once 'vendor/autoload.php';

use MicrosoftAzure\Storage\Blob\BlobRestProxy;

$filePath = 'dummy.png';
$connectionString = 'DefaultEndpointsProtocol=https;AccountName=XXXXX;AccountKey=XXXXX;EndpointSuffix=XXXXX';
$containerName = 'tutorial-container';

$content = fopen($filePath, 'r');

// Create blob client.
$blobClient = BlobRestProxy::createBlobService($connectionString);

// Upload blob.
$blobClient->createBlockBlob($containerName, $filePath, $content);
```
`$connectionString` には `DefaultEndpointsProtocol` から始まる接続文字列を指定する。
`$containerName` には作成したコンテナー名を指定する。


```terminal
C:\tutorial-azure-blob-storage>php upload.php
```

## エラー
>Fatal error: Uncaught Error: Call to undefined function GuzzleHttp\Psr7\stream_for() in C:\tutorial-azure-blob-storage\vendor\microsoft\azure-storage-blob\src\Blob\BlobRestProxy.php:1837 Stack trace: #0 C:\tutorial-azure-blob-storage\vendor\microsoft\azure-storage-blob\src\Blob\BlobRestProxy.php(1805): MicrosoftAzure\Storage\Blob\BlobRestProxy->createBlockBlobAsync('tutorial-contai...', 'dummy.png', Resource id #13, NULL) #1 C:\tutorial-azure-blob-storage\upload.php(16): MicrosoftAzure\Storage\Blob\BlobRestProxy->createBlockBlob('tutorial-contai...', 'dummy.png', Resource id #13) #2 {main} thrown in C:\tutorial-azure-blob-storage\vendor\microsoft\azure-storage-blob\src\Blob\BlobRestProxy.php on line 1837

`microsoft/azure-storage-blob` でコールされている `guzzlehttp/psr7` の `stream_for` 関数はバージョン 2.0.0 から `Utils::streamFor` メソッドへ置き換えられていることが原因。

https://github.com/guzzle/psr7#upgrading-from-function-api

>The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience:

## guzzlehttp/psr7 ダウングレード
`guzzlehttp/psr7` をダウングレードしてメジャーバージョンを下げる。

```diff_jsonnet
{
    "require": {
-       "microsoft/azure-storage-blob": "^1.5"
+       "microsoft/azure-storage-blob": "^1.5",
+       "guzzlehttp/psr7": "1.8.2"
    }
}
```

```terminal
C:\tutorial-azure-blob-storage>composer update
```

```terminal
C:\tutorial-azure-blob-storage>composer show
guzzlehttp/guzzle              7.3.0 Guzzle is a PHP HTTP client library
guzzlehttp/promises            1.4.1 Guzzle promises library
guzzlehttp/psr7                1.8.2 PSR-7 message implementation that also provides common utility methods
microsoft/azure-storage-blob   1.5.2 This project provides a set of PHP client libraries that make it easy to acce...
microsoft/azure-storage-common 1.5.1 This project provides a set of common code shared by Azure Storage Blob, Tabl...
psr/http-client                1.0.1 Common interface for HTTP clients
psr/http-message               1.0.1 Common interface for HTTP messages
ralouphie/getallheaders        3.0.3 A polyfill for getallheaders.
```

```terminal
C:\tutorial-azure-blob-storage>php upload.php
```
再実行、エラーが発生せずアップロードを確認できた。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243093/15a4e931-47ea-6eac-b94b-04c5665b7c50.png)

# アップロード後の確認
>https://（ストレージアカウント名）.blob.core.windows.net/（コンテナー名）/（BLOB名）

でアクセスできる。

:::note info
用途に合わせてコンテナーのパブリックアクセスレベルを「プライベート」「BLOB」「コンテナー」のいずれか設定する。
:::

前述のコードでアップロードを行った場合、対象の BLOB が画像や PDF といったブラウザでレンダリングできる種類であっても、ブラウザ表示ではなくダウンロードされてしまう。
これはアップロードの際、明示的に CONTENT-TYPE を指定しなければ自動的に `application/octet-stream` が設定される為である。

以下のようにコンテンツタイプを指定してやると良い。

```diff_php:upload.php
    <?php
    require_once 'vendor/autoload.php';

    use MicrosoftAzure\Storage\Blob\BlobRestProxy;
+   use MicrosoftAzure\Storage\Blob\Models\CreateBlockBlobOptions;

    $filePath = 'dummy.png';
    $connectionString = 'DefaultEndpointsProtocol=https;AccountName=XXXXX;AccountKey=XXXXX;EndpointSuffix=XXXXX';
    $containerName = 'tutorial-container';

    $content = fopen($filePath, 'r');

+   $options = new CreateBlockBlobOptions();
+   $options->setContentType('image/png');

    // Create blob client.
    $blobClient = BlobRestProxy::createBlobService($connectionString);

    // Upload blob.
-   $blobClient->createBlockBlob($containerName, $filePath, $content);
+   $blobClient->createBlockBlob($containerName, $filePath, $content, $options);
```


## 参考
https://qiita.com/SHIN_DEVELOP/items/ea6999e72bdce7e33df9