画像投稿機能を追加する

掲示板に画像投稿機能を追加してみましょう。

imageカラムの追加

まず、messagesテーブルにアップロード画像のファイル名を保存するためのimageカラムを追加するため、マイグレーションファイルを生成します。

php artisan make:migration add_image_to_messages_table --table=messages

上記のコマンドで、sample_app/database/migrationsに xxxx_add_image_to_messages_table.php というマイグレーションファイルが生成されます。

内容を追記して以下のようにしましょう。

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddImageToMessagesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('messages', function (Blueprint $table) {
            //
            $table->string('image');  //カラム追加
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('messages', function (Blueprint $table) {
            //
            $table->dropColumn('image');  //カラムの削除
        });
    }
}

マイグレーション実行時に行われるのが up() の処理になります。 ここでは文字列型の image というカラムを 追加しているのがわかります。

なお、downの処理はマイグレーションを取り消す時に行われる処理内容になります。 今回の処理の場合には、image カラムの追加を取り消す処理を書いておきます。

追記を終えてマイグレーションファイルを保存したら

php artisan migrate

上記のコマンドを実行してマイグレーションを行いましょう。

フォームに画像アップロード用の記述を追加

index.blade.php に以下のように追記します。 (form部分のみ抜粋します。)

index.blade.php

    {{-- enctypeの指定が必要です。 --}}
    <form method="post" action="{{ url("/messages") }}" enctype="multipart/form-data">
        {{-- LaravelではCSRF対策のため以下の一行が必須です。 --}}
        {{ csrf_field() }}

        <div>
            <label>
                名前:
                <input type="text" name="name" class="name_field" placeholder="お名前を入力">
            </label>
        </div>

        <div>
            <label>
                コメント:
                <input type="text" name="body" class="comment_field" placeholder="コメントを入力">
            </label>
        </div>

        {{-- ファイルアップロード用のinputを追加します。 --}}
        <div>
            <label>
                画像:
                <input type="file" name="image">
            </label>
        </div>

        <div>
            <input type="submit" value="投稿">
        </div>
    </form>

上記のように

  1. formタグに enctype="multipart/form-data" の属性を追加
  2. input type="file" でファイルアップロード用のinputを作成

という処理が必要になります。特に、1つめの処理はフォームで送信するテキストと、アップロードファイルという複数のパートでフォーム送信を行うために必須となりますので忘れないようにしましょう。

createメソッドに処理を追加

続いて、createメソッドに処理を追加します。

以下はcreateメソッドの抜粋です。

MessagesController.php

    public function create(Request $request){

        // requestオブジェクトのvalidateメソッドを利用。
        $request->validate([
            'name' => 'required|max:20', // nameは必須、20文字以内
            'body' => 'required|max:100', // bodyは必須、100文字以内
            'image' => [
                'file',
                'image',
                'mimes:jpeg,png',
                'dimensions:min_width=100,min_height=100,max_width=600,max_height=600',
            ], // ファイルアップロードが行われ、画像ファイルでjpeg,pngのいずれか、100x100~600x600まで
        ]);

        $filename = '';
        $image = $request->file('image');
        if( isset($image) === true ){
            // 拡張子を取得
            $ext = $image->guessExtension();
            // アップロードファイル名は [ランダム文字列20文字].[拡張子]
            $filename = str_random(20) . ".{$ext}";
            // publicディスク(storage/app/public/)のphotosディレクトリに保存
            $path = $image->storeAs('photos', $filename, 'public');
        }

        // Messageモデルを利用して空のMessageオブジェクトを作成
        $message = new \App\Message;

        // フォームから送られた値でnameとbodyを設定
        $message->name = $request->name;
        $message->body = $request->body;
        // ファイル名を保存
        $message->image = $filename;

        // messagesテーブルにINSERT
        $message->save();

        // メッセージ一覧ページにリダイレクト
        return redirect('/messages');
    }

バリデーションを行い、 問題ない場合には [ランダム文字列20文字].[拡張子] というファイル名で画像を保存しています。 (ファイル名の重複を防ぐと同時に、不正な文字の混入を防いでいます。)

messagesにINSERTする項目として、imageカラムも追加しているところにも注目しましょう。

ファイルの表示

では、続いて投稿したファイルを表示させましょう。 index.blade.php を編集します。

(出力部分を抜粋しています。)

index.blade.php

    <ul>
        @forelse($messages as $message)
            <li>
                @if($message->image !== '')
                    <img src="{{ asset('storage/photos/' . $message->image) }}">
                    <br>
                @endif
                {{ $message->name }}: 
                {{ $message->body }}  
                [{{ $message->created_at }}]
            </li>
        @empty
            <li>メッセージはありません。</li>
        @endforelse
    </ul>

画像ファイルがアップロードされているときは、 画像が表示されるように設定しています。

追記が完了したら

http://localhost/messages

(Docker Toolboxの場合は http://(dockerのipアドレス)/messages )

で表示を確認してみましょう。

表示を確認してみると、まだ画像が表示されず、 リンク切れになっているのがわかります。

これは、画像の保存先である sample_app/storage/ が非公開のフォルダだからです。

php artisan storage:link

上記のコマンドで、sample_app/storageから sample_app/public/storageに対してシンボリックリンクが貼られ、画像を公開できるようになります。

再度

http://localhost/messages

(Docker Toolboxの場合は http://(dockerのipアドレス)/messages )

を確認すると画像が表示されているのが確認できます。

Windowを利用している場合、php artisan storage:link が失敗することがよくあります。

こちらは、docker環境が管理者権限で準備されていないことが原因です。

以下の手順に従って、docker用の仮想環境を管理者権限で立ち上げましょう。

  1. コンテナからexitで抜ける
  2. docker-compose downで一旦コンテナを落とす。
  3. Docker QuickStart Terminalのウィンドウを、バツをクリックして消す
  4. プログラム一覧からVirtualBoxを立ち上げ、defaultという仮想マシンがあるので、右クリック→閉じる→電源オフで仮想マシンの電源を落とす。
  5. Docker QuickStart Terminalのショートカットを右クリックし、「管理者として実行」で立ち上げる
  6. クジラのAAが出てきたら、VSCodeのターミナルに戻り、docker-compose up -d mysql nginx workspace でコンテナを立ち上げる。
  7. 以上で準備完了です。通常通りコンテナ内に入ってphp artisan storage:linkを実行すればリンクが貼れるはずです。

results matching ""

    No results matching ""