Laravelとvue.jsを利用したファイルアップロードの手順を説明しています。アップロードするファイルのチェックなど何も行っていないため実用的ではありません。しかし、Laravel+vue.js環境でのファイルアップロード方法の基本となる部分なのでぜひこの機会に理解を深めてください。

MAC環境で動作確認を行っています。

環境の構築

Laravel, vue.jsのインストールを行い、データベース、アップロードファイルを保存するためのテーブルの作成を行います。環境構築についての手順が必要でない場合はこの章はスキップしてください。

Laravelのインストール

composerを利用してLaravelのインストールを行います。


$ composer create-project --prefer-dist laravel/laravel laravel5.8

インストールが完了したらLaravelの開発用サーバを起動してブラウザでhttp://127.0.0.1:8000にアクセスしてください。


$ php artisan serve
Laravel development server started: 

下記の画面が表示されることを確認します。

Laravelインストール後の画面
Laravelインストール後の画面

vue.jsのインストール

Laravelのインストールが完了したら、npmを利用してvue.jsを含めたJavaScriptのライブラリのインストールを行います。Laravelのインストールでイィレクトリで実行します。


$ cd laravel5.8/
$ npm install

JavaScriptのライブラリのインストールが完了したら、npm run devコマンドを実行してビルドが正常に動作するか一度確認します。


$ npm run dev

vue.jsのコンポーネントファイルExampleComponent.vueの内容がブラウザに表示されるようにwelcome.blade.phpの書き換えを行います。


<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>Laravel Vue FileUpload</title>
<link rel="stylesheet" href="{{ mix('css/app.css') }}">
</head>
<body>
    <div id="app">
        <example-component></example-component>
    </div>
<script src="{{ mix('js/app.js') }}"></script> 
</body>
</html>

ExapleComponent.vueも書き換えを行い、ファイルをアップロードするためのinput要素とbutton要素を追加します。


<template>
    <div class="content">
        <h1>File Upload</h1>
        <p><input type="file"></p>
        <button>アップロード</button>
    </div>
</template>

<script>
    export default {

    }
</script>

<style>
.content{
    margin:5em;
}
</style>

書き換えが終わったら、再度npm run devを実行します。


$ npm run dev

ブラウザでアクセスし、下記のファイルアップロード画面が表示されたらアップロードしたファイルの情報を保存するテーブルの作成を行います。

ファイルアップデートページ
ファイルアップデートページ

データベースの設定

アップロードしたファイルのパスを保存するためにデータベースを作成し、テーブルの作成を行います。データベースはsqliteを使用します。以下のコマンドでsqliteデータベースを作成します。


$ touch database/database.sqlite

.envファイルでデータベースの接続に関する設定をmysqlからsqliteに変更します。

変更前はmysqlの設定になっています。


DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret

変更後は下記のようになります。DB_CONNECTION以外は削除します。


DB_CONNECTION=sqlite

テーブルはLaravelがデフォルトで準備しているusersテーブルを使用します。usersのマイグレーションファイルにファイルのパスを保存する列(file_path)を追加します。

マイグレーションファイルはdatabase/migrationsの2014_10_12_000000_create_users_table.phpです。passwordの列の下にfile_pathを追加します。


public function up()
{
Schema::create('users', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->string('name');
    $table->string('email')->unique();
    $table->timestamp('email_verified_at')->nullable();
    $table->string('password');
    $table->string('file_path');
    $table->rememberToken();
    $table->timestamps();
});
}

php artisan migrateコマンドでテーブルを作成してください。


い $ php artisan migrate
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table

tinkerを使用して、ユーザの作成を行います。


$ php artisan tiker
>>> $user = new User
=> App\User {#2969}
>>> $user->name = 'john'
=> "john"
>>> $user->email = 'john@example.com'
=> "john@example.com"
>>> $user->password = bcrypt('password')
=> "$2y$10$ugq8xVC6CX.xjlztHvB8buN.3XvgJYPIHlxAOiqtp2a.P9lhQq1vC"
>>> $user->save()
=> true

ここまでの処理で環境の構築は完了です。

ファイルアップロードの設定

ファイルをアップロードする環境が整ったので、アップロードする手順に進みます。

ユーザが選択したファイル情報の取得

ユーザがファイル選択を行った時にvue.js側で選択したファイル情報の取得を行います。

input要素にv-onディレクティブのchangeイベントを設定してファイルが選択されたかどうかを監視します。ファイル選択が行われるとfileSelectedメソッドが実行されます。


<p><input type="file" v-on:change="fileSelected"></p>

scriptタグにメソッドfileSelectedを追加し、event変数を利用して選択したファイル情報を取得します。


export default {
    methods:{
        fileSelected(event){
            console.log(event)
        }
    }
}

ファイルを選択し、デベロッパーツールのコンソールで確認するとeventから選択されたファイルの情報を取得することができます。

event情報を取得
event情報を取得

eventのtargetの中のfilesに選択したファイルの情報が入っています。

eventのtargetからファイル情報を取得
eventのtargetからファイル情報を取得

このファイル情報を保存するためにdataにfileInfoを追加して取得したファイルの情報をfileInfoの中に保存します。


export default {
    data: function(){
        return {
          fileInfo: ''
        }
    },
    methods:{
        fileSelected(event){
            this.fileInfo = event.target.files[0]
        }
    }
}

ファイルのアップロード設定

ファイル情報の取得の設定が完了したので、アップロードボタンをクリックするとファイルアップロードが開始される設定を追加します。

buttonにv-onディレクトリのclickイベントを設定し、メソッドの名前はfileUploadとします。ボタンをクリックするとfileUploadメソッドが実行されます。


<p><input type="file" v-on:change="fileSelected"></p>
<button v-on:click="fileUpload">アップロード</button>

fileUploadメソッドの中でformDataオブジェクトとaxiosを利用してファイルのアップロードを行います。

Laravelの初期設定でaxiosは使用可能なので、追加インストールは必要ありません。

ファイルをアップロードする場所/api/fileuploadの設定を行っていないので、ファイルを選択しアップロードボタンを押すと404NotFoundエラーが発生します。


fileUpload(){
    const formData = new FormData()

    formData.append('file',this.fileInfo)

    axios.post('/api/fileupload',formData).then(res =>{
        console.log(res)
    });
}

Laravelでのルーティング設定

/api/fileuploadのルーティング設定をLaravel側で行います。api.phpを開いて/api/fileuploadの追加を行います。ファイルの情報が送信されてきているか確認するためにdd関数を使って送信されている内容を確認します。


Route::post('fileupload',function(){
	dd(request()->all());
});

ファイルがLaravel側に送信されていることが確認できます。

ファイルの送信状況をLaravel側で確認する
ファイルの送信状況をLaravel側で確認する

ファイルの保存

Laravel側にファイルが送信されていることが判明したのでファイルの保存を行います。ファイル名はgetClientOriginalName()で取得できます。storageAsメソッドを使うとstorage/app/publicの下にファイルが保存されます。


Route::post('fileupload',function(){

	$file_name = request()->file->getClientOriginalName();

	request()->file->storeAs('public/',$file_name);

});

storage/app/publicディレクトリを確認すると選択したvue.pngファイルが保存されていることが確認できます。


$ ls
vue.png

保存された場所はブラウザを通してアクセスできない場所のため公開ディレクトリのpublicにシンボリックリンクを貼る必要があります。php artisan storage:linkコマンドを実行するとシンボリックリンクが貼られます。


 $ php artisan storage:link
The [public/storage] directory has been linked.
シンボリックリンクの設定により/public/storageにアクセスすると/storage/app/publicにアクセスすることになります。ショートカットと同じです。Laravelでは/pubic以下のディレクトリが公開ディレクトリになります。

シンボリックリンクが貼られたので、ブラウザでhttp://127.0.0.1:8000/storage/vue.pngにアクセスするとアップロードされたファイルを確認することができます。

テーブルへのパスの保存

ブラウザからアクセスさせるためには/storage/ファイル名でテーブルのfile_path列にパス情報を登録する必要があります。

usersテーブルのfile_path列に保存を行うので、どのユーザのfile_pathに保存するのか保存するユーザを識別する必要があります。今回はファイルアップロードを目的にしているので、ユーザID1をもつユーザのfile_pathにアップロードしたファイルのパス(/storage/ファイル名)を保存します。

作成したユーザの情報をtinkerなどを利用して確認してIDの設定を行ってください。ユーザを再作成した場合などはIDは1ではありません。

追加したfile_path列に書き込みを許可するためにモデルファイルUser.phpのfile_pathを$fillable変数に追加する必要があります。


    protected $fillable = [
        'name', 'email', 'password','file_path'
    ];
アップロードの処理は正常に動作しているのにfile_pathに値が入らない場合は、上記の設定を確認してください。

ユーザID1のfile_pathにパスの更新を行うためにルーティングファイルapi.phpを以下のように書き換えます。更新が完了すると$user情報を戻します。


Route::post('image',function(){

	$file_name = request()->uploadfile->getClientOriginalName();

	request()->uploadfile->storeAs('public/',$file_name);

	$user = User::find(1);

	$user->update(['file_path' => '/storage/'.$file_name]);

	return $user;
});

ファイルの送信を行い、コンソールで$userの情報が戻ってきており、file_pathに送信したファイルの情報が入っているか確認します。下記ではfile_pathに/storage/vue.pngを確認することができます。

戻り値の$userの内容の確認
戻り値の$userの内容の確認

file_pathにファイルのパスの情報が入っていたら、サーバへのファイルの保存とデータベースへのファイルのパス情報の保存は完了です。

ここからは戻り値$userを使ってvue.js側でどのような処理を行うかがポイントになります。

vue.js側の処理

本文書では、戻り値の$userを使用して、アップロードしたファイルの画像をブラウザに表示させる処理を追加します。

新規にdataにuserとshowUserImageのデータを追加します。userには、戻ってきたuser情報、showUserImageはv-showディレクティブで画像の表示・非表示を切り替えるために使用します。


data: function(){
    return {
      fileInfo: '',
      user: '',
      showUserImage: false
    }
},

axiosでファイルの送信を行い、戻ってきた値をuserデータに入力します。userデータのfile_pathに値が設定されていたら、showUserImageの値をtrueに変更します。


axios.post('http://127.0.0.1:8000/api/fileupload',formData).then(response =>{
    this.user = response.data
    if(response.data.file_path) this.showUserImage = true
});

template側ではv-showディレクティブを使用して、画像の表示・非表示を切り替えます。


<div class="content">
    <h1>File Upload</h1>
    <p><input type="file" v-on:change="fileSelected"></p>
    <button v-on:click="fileUpload">アップロード</button>
    <p v-show="showUserImage"><img v-bind:src="user.file_path"></p>
</div>v-on:click="fileUpload">アップロード</button>

アップロードが完了するとアップロードした画像が表示されます。

アップロードした画像の表示
アップロードした画像の表示

選択したファイルのチェックも行わない簡易的なコードですが、vue.jsをフロントエンドとしてLaravelと連携して、サーバへのファイルのアップロードとusersテーブルへのファイルパスの設定、アップロードしたファイルの表示まで行うことができました。これを元にユーザフレンドリーな実用的なファイルアップロードにチャレンジしてみてください。

今回更新したExampleComponent.vue内のコードの全体は以下となります。


<template>
    <div class="content">
        <h1>File Upload</h1>
        <p><input type="file" v-on:change="fileSelected"></p>
        <button v-on:click="fileUpload">アップロード</button>
        <p v-show="showUserImage"><img v-bind:src="user.file_path"></p>
    </div>
</template>

<script>
export default {
    data: function(){
        return {
          fileInfo: '',
          user: '',
          showUserImage: false
        }
    },
    methods:{
        fileSelected(event){
            this.fileInfo = event.target.files[0]
        },
        fileUpload(){
            const formData = new FormData()

            formData.append('file',this.fileInfo)

            axios.post('http://127.0.0.1:8000/api/fileupload',formData).then(response =>{
                this.user = response.data
                if(response.data.file_path) this.showUserImage = true
            });
        }
    }
}
</script>

<style>
.content{
    margin:5em;
}
</style>