Laravelでファイルのアップロード・ダウンロードのテスト
Laravelでファイルのアップロード・ダウンロードのテストを試していたら躓いたことが多かったので備忘録です。
環境
- PHP: 7.4.7
- Laravel: 7.15.0
事前準備
ControllerやModel、ルーティングは以下のとおりです。
また、ファイル保存先の設定としてはconfig/filesystems.php
で使用するdiskをpublic
に設定しているので、
ファイルがアップロードされた時に、storage/app/public
以下にあるtest
ディレクトリ以下にファイルを保存します。
ルーティング
<?php use Illuminate\Support\Facades\Route; Route::get('/file', 'FileController@index')->name('file.index'); Route::post('/file', 'FileController@upload')->name('file.upload'); Route::get('/file/{fileId}', 'FileController@download')->name('file.download');
一つのページでアップロードとダウンロードが出来る機能を有しているものを想定しています。
Model
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\UploadedFile; class File extends Model { protected $fillable = [ 'name', 'path' ]; public function saveFile(UploadedFile $file) { $name = $file->getClientOriginalName(); $path = $file->store('test'); return $this->fill(compact('name', 'path'))->save(); } }
Filesテーブルにファイル名とパスを保存します。
File Storage - Laravel - The PHP Framework For Web Artisans
Controller
<?php namespace App\Http\Controllers; use App\Models\File; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; class FileController extends Controller { public function index() { $files = File::all(); return view('file.index', compact('files')); } public function upload(Request $request) { $file = new File(); $file->saveFile($request->file('upload-file')); return redirect(route('file.index')); } public function download(Int $fileId) { $file = File::find($fileId); return Storage::download($file->path, $file->name); } }
アップロード時はModelに記述した処理で保存し、
ダウンロード時はDBに保存した情報をもとにしてファイルを返します。
PDFや画像をダウンロードせずにブラウザで表示したい場合は以下のように記述します。
$file = File::find($fileId); $headers = [ 'Content-Disposition' => 'inline; filename="' . $file->name . '"' ]; return response()->file($file->path, $headers);
File Storage - Laravel - The PHP Framework For Web Artisans
テスト
<?php namespace Tests\Feature; use App\Models\File; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Http\UploadedFile; use Illuminate\Support\Facades\Storage; use Tests\TestCase; class FileControllerTest extends TestCase { use RefreshDatabase; public function testFileUpload() { Storage::fake('public'); $file = UploadedFile::fake()->image('temp.jpg'); $this->post(route('file.upload'), [ 'upload-file' => $file ]); Storage::disk('public')->assertExists('test', $file->hashName()); } public function testFileDownload() { Storage::fake('public'); $file = UploadedFile::fake()->image('temp.jpg'); $this->post(route('file.upload'), [ 'upload-file' => $file ]); $uploadedFile = File::first(); $response = $this->get(route('file.download', ['fileId' => $uploadedFile->id])); $this->assertTrue($response->headers->get('content-disposition') == 'attachment; filename=' . $uploadedFile->name); } }
アップロード
テスト用のdiskとしてpublic
を、テスト終了後削除されるデータとしてtemp.jpg
を作成します。
ファイルのpostを実行した後、public
以下のtest
(保存先にModelで指定したdisk)にtemp.jpgが存在することを確認します。
ファイル名はstoreAs()
で指定しない限り一意なものになっています。
ダウンロード
アップロードと同様に、一度ファイルをアップロードして、
ダウンロードされたファイル名が保存したファイルであることを確認しています。
実行
ひとまずテストを実行してみると問題なく通ることが確認出来ます。
# php artisan test PASS Tests\Feature\FileControllerTest ✓ file upload ✓ file download Tests: 2 passed Time: 2.62s
CircleCI
CircleCIのconfig.ymlも記載します。apt-getでインストールしているものは最低限のものです。
また、CircleCIで用いる設定ではDB_HOST
を127.0.0.1
にしておくことは忘れがちなので注意です。
version: 2 jobs: build: docker: - image: circleci/php:7.4-apache - image: postgres:alpine environment: POSTGRES_DB: test POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres environment: - APP_DUBUG: true - APP_ENV: testing working_directory: ~/laravel steps: - checkout - run: name: Update apt-get command: sudo apt-get update - run: sudo apt-get install -y libpq-dev libjpeg-dev libpng-dev - run: sudo docker-php-ext-configure gd --with-jpeg - run: sudo docker-php-ext-install pdo pdo_pgsql gd - run: name: Setup Laravel testing environment variables for CircleCI test command: cp .env.testing .env - restore_cache: keys: - composer-v1-{{ checksum "composer.json" }} - composer-v1- - run: composer install -n --ignore-platform-reqs - save_cache: key: composer-v1-{{ checksum "composer.json" }} paths: - vendor - run: name: Run Phpunit command: php artisan test
まとめ
- Storage::fake()のあたりが公式ページ通りに書いても上手く行かず、だいぶ手間取ってしまっていた
filesystems.php
でどこに保存先を指定しているのかをちゃんと理解しておく必要がある
失敗談
- アップロードしたファイルのパスを間違って
storage/
以下ではなく、public/
以下にしてしまったことでローカルではテストが成功するのにCircleCIでは失敗するという事態になった php artisan storage:link
をCircleCIで実行することで解決はしたが、そもそもファイルの保存先を理解できていなかったのが問題storage/framework/testing
以下にテスト時のファイルが作成されるので、それを見ながら動作を確認することでデバッグしていた