Laravel でファイルを外部ストレージ(SFTP)へ保存・取得する全13実例

さてさて、LaravelはPHPでウェブサイトを構築するフレームワークとしてはほぼ王道の選択肢といった雰囲気すら持ち始めているように感じています。

それは、やはりLaravelがインストールした時点で様々な機能を持っているからなのですが、実はLaravelには単体のウェブサイトのためのシステムだけではなく、他のサーバーやストレージと連携する機能も持っているのをご存知でしょうか。

例えば、サイトが大規模になってきた場合にしばしば必要になってくるのが「分散ストレージ」です。ちょっと言葉は難しいですが、簡単にいうと「ファイルは別サーバーで管理したほうがいいよね」ということです。

メリットとしては、大容量でも比較的安価に利用できるHDDのサーバーを選択することもできますし、もしそのサーバーの容量を増やしたくなった場合でも、本サイト自体を移転することなくファイルサーバーだけを移転させることもできます。また、本サイトとファイルサーバーでそれぞれ同じファイルを保存し、バックアップシステムにすることもできます。

ということで、今回はSFTPを使ってファイルを外部ストレージで管理する方法を紹介します。

ぜひ参考にしてみてくださいね。

※ 実行環境: Laravel 5.7

パッケージをインストールする

LaravelでSFTPを使うには league/flysystem-sftp というパッケージが必要になります。次のコマンドでインストールしましょう。

composer require league/flysystem-sftp:~1.0

※ このパッケージはLaravelに組み込まれているのでServiceProviderなどを登録する必要はありません。

コンフィグにSFTPドライバーを追加する

ではconfig/filesystems.phpを開いてSTFPの設定を追加しましょう。

'disks' => [

    // 省略

    'sftp' => [
        'driver' => 'sftp',
        'host' => '***.***.***.***',
        'username' => '(ユーザー名)',
        'password' => '(パスワード)',
        'privateKey' => '(プライベートキーへのパス)',
        'port' => 22,
        'root' => '/home/username/storage',
        'timeout' => 30,
    ],

],

各項目は次のとおりです。

  • driver ・・・ ファイルシステムのドライバー名。
  • host ・・・ 外部ストレージのIPアドレス、もしくはドメイン名。
  • username ・・・ アクセスするユーザーの名前。
  • password ・・・ アクセスするユーザーのパスワード。
  • privateKey ・・・ 鍵認証のプライベートキー(ファイル)へのパス。省略可。
  • port ・・・ 接続するポート番号。省略可(デフォルト: 22)。
  • root ・・・ 接続後にベースになるパス。省略可。
  • timeout ・・・ タイムアウトまでの時間。

準備はこれで完了です。

※ なお、privateKeyですが形式が合わないなどの理由で接続がうまくいかないことがあるようです。その場合は、次のようにprivateKeyは削除してユーザー名とパスワードだけで実行してみてください。

'sftp' => [
    'driver' => 'sftp',
    'host' => '***.***.***.***',
    'username' => '(ユーザー名)',
    'password' => '(パスワード)',
    // 'privateKey' => '(プライベートキーへのパス)'
],

外部ストレージへファイルを保存する

では、実際に外部ストレージへファイルを作成してみましょう。

文字列を指定して保存

例えば、「テスト文字列」というデータのtest.txtというファイルを作成してみましょう。

\Storage::disk('sftp')->put('test.txt', 'テスト文字列');

SSHで確認してみます。

事前に実行したlsではファイルは表示されていませんが、実行後はtest.txtというファイルが作成されました。

そして、中身を確認すると文字列も正しく保存されています。

ローカル環境にあるファイルをコピーして保存する

では、次にすでに保存されているファイルを外部ストレージへ保存してみましょう。

use Illuminate\Http\File;

// 省略

$file = new File(storage_path('app/images/test.png'));
\Storage::disk('sftp')->putFile('', $file);

実際に確認してみるとこうなります。

ここで重要なのがputFile()の場合、ランダムなファイル名になるというところです。そのため、もし保存されたファイル名(実際にはパス)を取得したい場合は次のように返り値を利用します。

$filename = \Storage::disk('sftp')->putFile('images', $file);

また、指定したフォルダの中で下層フォルダを作り、その中へファイルをコピーしたい場合は、次のように第1引数に階層を指定します。

\Storage::disk('sftp')->putFile('images/profile', $file);

これで、/(ルートフォルダ)/images/profile/******というファイルが作成されることになります。

ローカル環境にあるファイルをファイル名を指定して保存する

先ほどの例はランダムなファイル名でファイルを保存しましたが、もちろん自分で名前をつけたい場合もあるでしょう。

その場合はputFileAs()を使います。

use Illuminate\Http\File;

// 省略

$file = new File(storage_path('app/images/test.png'));
\Storage::disk('sftp')->putFileAs('images/profile', $file, 'xxx.png');

実際に確認したものがこちらです。

※ なお、第1引数は先ほどと同じく階層を指定することができ、返り値はファイル名(パス)になります。

ファイルデータを追加する

例えば、外部ストレージに「テスト文字列」というデータを持ったテキストファイルが保存してあり、このファイルの先頭、もしくは最後に新しくテキストを追加したい場合です。

実際の例を見てみましょう。

\Storage::disk('sftp')->prepend('test.txt', '先頭に追加');
\Storage::disk('sftp')->append('test.txt', '最後に追加');

ファイル内を確認すると、次のようになりました。

※ 外部ストレージへログを保存するなどに使うといいかもしれません。

フォーム送信されたファイルを外部ストレージへ保存する

例えば、以下のようにフォームからファイルを送信し、そのファイルをローカルではなく外部ストレージへ保存する場合です。

<form method="POST" action="sftp_upload" enctype="multipart/form-data">
    @csrf
    <input type="file" name="profile_photo">
    <button type="submit">送信</button>
</form>

この場合ファイルデータを$requestから取得してsftpでアップロードする形になります。

public function sftp_upload(Request $request) {

    $file = $request->file('profile_photo');
    \Storage::disk('sftp')->putFile('images/profile', $file);
    \Storage::disk('sftp')->putFileAs('images/profile', $file, 'xxx.png');

}

実際に確認したものがこちらです。

外部ストレージからファイルを取得する

続いて、外部ストレージからファイルを取得する方法です。

ファイルの中身を取得する

例えば、すでに外部ストレージにtest.txtというファイルが存在していて、そのテキストファイルの「中身」を取得したい場合です。

$text = \Storage::disk('sftp')->get('test.txt');

この場合、$textの中にファイルの中身が入ってきます。

そのため、もし画像をdataURLにしてインライン指定したい場合は次のように画像データを変換すればいいでしょう。

$image = \Storage::disk('sftp')->get('images/profile/xxx.png');
$data = 'data: image/png;base64,'. base64_encode($image);
echo '<img src="'. $data .'">';

ファイルをダウンロードさせる

例えば、外部ストレージにあるtest.txtというファイルをユーザーにダウンロードさせたい場合です。この場合download()を使います。

return \Storage::disk('sftp')->download('test.txt');

ここで重要なのが、returnの部分です。download()の中身はレスポンスなのでコントローラーやルーティング内でreturnしてあげる必要があります。

そして、もしダウンロードするファイル名を指定する場合は第2引数に、ヘッダーを指定する場合は第3引数をそれぞれ指定します。

return \Storage::disk('sftp')->download('test.txt', 'new_name.txt', [
    'Your-Header' => 'v(^o^)'
]);

ファイルを削除する

外部ストレージのファイルを削除するには次のようにdelete()を使います。

\Storage::disk('sftp')->delete('test.txt');

また、フォルダを削除することもできます。

\Storage::disk('sftp')->delete('images'); // 「images」フォルダを削除

ファイルが存在しているかをチェックする

外部ストレージにすでにファイルが存在しているかどうかをチェックするにはexists()を使います。

if(\Storage::disk('sftp')->exists('images/profile/xxx.png')) {

    echo 'ファイルが存在しています!';

}

また、フォルダの存在チェックもできます。

if(\Storage::disk('sftp')->exists('images/profile')) {

    echo 'フォルダが存在しています!';

}

外部ストレージのフォルダを操作する

特定フォルダにあるファイル名(パス)を取得する

例えば、外部ストレージにあるimages/profileフォルダの中に保存されているファイルの名前(実際にはパス)を取得する場合です。

$files = \Storage::disk('sftp')->files('images/profile');
dd($files);

この場合、結果は次のように配列で返ってきます。

また、もしそのフォルダ内にある全てのファイル(つまり、サブフォルダ内も含めた全ファイル)を取得する場合はallFiles()を使います。

\Storage::disk('sftp')->allFiles('images');

中身はfiles()と同じく配列で返ってきます。

特定フォルダにあるサブフォルダを取得する

例えば、imagesフォルダの中に以下3つのサブフォルダが存在しているとします。

  • profile
  • pdf
  • text

この3つのフォルダ名(パス)を取得するにはdirectories()を使います。

$directories = \Storage::disk('sftp')->directories('images');
dd($directories);

実際に取得した例です。
files()と同じく配列で情報を取得することができます。

また、そのフォルダに保存されている全てのフォルダ(つまり下の階層も含めたフォルダ)を取得したい場合はallDirectories()を使います。

\Storage::disk('sftp')->allDirectories('images');

フォルダを作成する

例えば、外部ストレージにjsonという名前のフォルダを作成する場合です。

\Storage::disk('sftp')->makeDirectory('json');

フォルダを削除する

例えば、外部ストレージにあるjsonという名前のフォルダを削除する場合です。

\Storage::disk('sftp')->deleteDirectory('json');

おわりに

ということで、今回はsftpを通して外部ストレージへアクセスし、様々な操作を実行する手順をまとめてみました。

今回のテクニックを応用すればLaravelのスケジュールなどで定期的にデータベースのバックアップを外部ストレージで保存させておくことができますし、画像を投稿するサイトなどでは外部ストレージに画像を保存し、さらにサブドメインのURLから直接その画像へアクセスするようにすれば、本サイトへの不可軽減に貢献することができるのではないでしょうか。

そのため、今回のテクニックは特にデータ容量を気にするようなサイトに有効と言っていいでしょう。

ぜひ皆さんも今回の記事をサイトのスケールアップに役立ててくださいね。

ではでは!