Laravel + Guzzle を使用し、テキストや画像を混在させた状態のデータをHTTP Client経由で送りたいときの備忘録です。検証はしてませんがPDFやテキストファイルなど、その他のファイル形式でもいけるはずです。
APIでの画像アップロード(ファイルアップロード)のお供にどうぞ。
検証環境
- Laravel 6.20.12
- Guzzle 7.2
- PHP 7.4
前提条件: base64 と multipart/form-data
ファイルの送信にはよく base64
へのエンコードが用いられますが、Guzzleで画像やその他ファイルをエンコードせずに送るためには multipart/form-data
という形式で送る必要があるようです。
base64
はざっくり言えば、64進数を意味しており、データを64種類の文字 (アルファベット[ a - z ], [ A - Z ], 数字[ 0 - 9 ]、一部の記号[ +, / ] )で表すエンコード方式です。
Base64 出典: フリー百科事典『ウィキペディア(Wikipedia)』
「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典 - Base64
base64ってなんぞ??理解のために実装してみた
multipart/form-data
は Content-Type
(メディアの種別を表すもの)の一種で複合データ型を意味し、HTMLの送信フォームでファイルをアップロードしたいときなどによく見かけます。
下記のフォームでは enctype
として multipart/form-data
を設定しています。この設定がないと画像やPDFなど諸々ファイルアップロードができないので、設定を忘れてフォームが動かなかった、という経験のある方も多いのではないでしょうか。
<form action="/" method="post" enctype="multipart/form-data">
<input type="text" name="foo" value="foobar">
<input type="file" name="fooFile">
<button type="submit">送信</button>
</form>
HTML フォームにおける Content-Type - MDN - Mozilla
【HTTP】multipart/form-data の boundary って何ぞや?
Multipart/form-dataの仕様メモ
[フロントエンド] multipart/form-dataを理解してみよう
SuikaWiki > Wiki > multipart/form-data (MIME)
Guzzle公式ドキュメントより
まずはコチラの公式ドキュメントを見てみます。英語だし端的にしか書かれていないので色々と不安になりますが、一度書いて動かしてしまえば大丈夫です。
Guzzle Docs: Request Options - multipart
multipart のセクションで、以下のように書いてあります。
Sets the body of the request to a multipart/form-data form.
(中略)
The value of multipart is an array of associative arrays, each containing the following key value pairs:
- name: (string, required) the form field name
- contents: (StreamInterface/resource/string, required) The data to use in the form element.
- headers: (array) Optional associative array of custom headers to use with the form element.
- filename: (string) Optional string to send as the filename in the part.
リクエストのボディを multipart/form-data
形式で設定します。
(中略)
multipartの値は連想配列の配列であり、それぞれに次のキーと値のペアが含まれています。
- name:(string, 必須)フォームのフィールド名
- contents:(StreamInterface/resource/string, 必須)form要素で使用するデータ。
- headers:(array)フォーム要素で使用するカスタムヘッダーのオプションの連想配列。
- filename:(string)パーツのファイル名として送信するオプションの文字列。
オプションという表現も、慣れていないと「つまりどういうことだってばよ」という気持ちになったりしますが、元の英文の headers
と filename
に Optional と書いてあり、そういうときは大体、必須では無い、あってもなくても良い、必要に応じて入れる、という意味になります。
なので、今回の実装で必須なのは name
と contents
だということになります。
実装
Laravel 7 以上のバージョンでは、デフォルトでGuzzleのパッケージを含むそうで、使い方は以下のドキュメントに書かれています。
今回はLaravel 6 以下での話になるので、Laravel 6 以下のバージョンは composer
コマンドで guzzlehttp/guzzle
パッケージのインストールを行います。
composer require guzzlehttp/guzzle
簡単に書くと、Guzzleを使ったリクエスト(画像ファイル送信)はこんな感じです。
一度Storageに保存した画像をGuzzleで読み込みさせるように実装しました。
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7;
public function postApi($data)
{
$method = 'POST';
$api_url = 'https://example.com/v1/upload';
$params =
[
'name' => 'token',
'contents' => Auth::user()->token
],
[
'name' => 'product_id',
'contents' => $data['product_id']
],
[
'name' => 'image_file',
'contents' => Psr7\Utils::tryFopen('../' . Storage::url($data['file_path']))
];
$client = new Client();
$response = $client->request($method, $api_url, ['multipart' => $params, 'http_errors' => false]);
return $response;
}
headers/Content-Typeについて
Guzzle のリクエストタイプには query
や form_params
を使用することの方が多いかもしれませんが、画像をそのままファイルとして送信したいときは multipart
を使用します。
Stack Overflow など見ていると http における headers の内容を配列で別途指定している方もいるようですが、Guzzle ではリクエスト方式に multipart
を選んだ時点で Content-Type
が multipart/form-data
になるため、あえて headers の Content-Type
を手動で再設定する必要はありません。
認証などで headers をカスタマイズしなければいけないときのみ手動で設定してください。またその際は、Content-Type
を誤った形式で上書きしないように注意してください。
パラメータについて
name
にはAPIのパラメータ名(フィールド名)、contents
にはパラメータとして送りたい内容を入れます。
multipart
のときは、ただのテキスト情報も、画像などのファイルも、一緒に送れます。
filename
には任意でファイル名を入れられますが、なくても大丈夫です。
ファイルパスについて
Guzzleが app
ディレクトリと同じ並びの vendor
ディレクトリにあるせいなのか、 Storage::url()
だけだとパスエラーになったため、パスの先頭に ../
を付けています。(他にもっと良い書き方をご存知の方がいたら教えてくださいmm)
その他のパラメータなど
ほかに解説記事が豊富にあってここで説明するほどでもないかなと思った項目は飛ばしていますが、「この設定が知りたい」といったリクエストが万が一あれば(素人の解説でよければ)追記します。
という訳で今日はいったんここまで。
不備不足にお気付きの方はご指摘ください。
参考URL
Guzzle Docs: Request Options - multipart
POST - MDN - Mozilla
PHP GuzzleHttp. How to make a post request with params?
Multiple files uploaded via Guzzle multipart/form-data request are not recognized by Symfony
Sample POST request with Guzzle
【PHP】GuzzleでPOSTリクエストができなかったお話し
PHP Http ClientのGuzzleパッケージを使う
今時のPHP HTTPクライアントのGuzzleを使ってみた
Guzzleを使って外部APIへPOST送信