Laravel8 CRUD処理を使った投稿アプリを作成する その9 画像をアップロードできるようにする

# 目的

- 投稿内容と一緒に画像を投稿する方法をまとめる

# 実施環境

- 筆者の実施環境を記載する。
- ハードウェア環境

| 項目 | 情報 |
| --- | --- |
| OS | macOS Catalina(10.15.5) |
| ハードウェア | MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports) |
| プロセッサ | 2 GHz クアッドコアIntel Core i5 |
| メモリ | 32 GB 3733 MHz LPDDR4 |
| グラフィックス | Intel Iris Plus Graphics 1536 MB |

- ソフトウェア環境

| 項目 | 情報 | 備考 |
| --- | --- | --- |
| PHP バージョン | 7.4.8 | Homebrewを用いてこちらの方法で導入→[Mac HomebrewでPHPをインストールする](https://qiita.com/miriwo/items/cd54077aad7e139cf518) |
| Laravel バージョン | 8.6.0 | commposerを用いてこちらの方法で導入→[Mac Laravelの環境構築を行う](https://qiita.com/miriwo/items/adaaaeecae69d6392e08) |
| MySQLバージョン | 8.0.19 for osx10.13 on x86_64 | Homwbrewを用いてこちらの方法で導入→[Mac HomebrewでMySQLをインストールする](https://qiita.com/miriwo/items/ef6a064bc545fbea29d9) |
| Node.jsバージョン | v12.14.1 | Homwbrewを用いてこちらの方法で導入→[Mac HomebrewでNode.jsをインストールする](https://qiita.com/miriwo/items/73d1546220f1c091a7d5) |

# 前提条件

- 下記の記事の作業が完了していること。
    - [Laravel8 CRUD処理を使った投稿アプリを作成する その8 ベースレイアウトの作成と反映](https://qiita.com/miriwo/items/e874bf162a5f3d58f1c7)

# 前提情報

- 作成するアプリ名は「laravel8_crud」とする。
- 作成するMySQLのデータベース名は「laravel８_crud_DB」とする。
- 下記に今回の作業のあとのソースコードのリモートリポジトリのリンクを記載する。
    - [https://github.com/miriwo0104/laravel8_crud/tree/No09](https://github.com/miriwo0104/laravel8_crud/tree/No09)

# 概要

1. .envファイルの記載
1. シンボリックリンクの作成
1. マイグレーションファイルの記載とマイグレート
1. コントローラファイルの修正
1. ベースレイアウトファイルの作成
1. ビューファイルの編集
1. 確認

# 詳細

1. .envファイルの記載
    1. laravel8_crudディレクトリで下記コマンドを実行して.envファイルを開く。

        ```terminal
        $ vi .env
        ```
    1. 下記の内容を.envファイルに追記する。

        ```laravel8_crud/.env
        FILESYSTEM_DRIVER=public
        ```
1. シンボリックリンクの作成
    1. laravel8_crudディレクトリで下記コマンドを実行してシンボリックリンクを作成する。

        ```terminal
        $ php artisan storage:link
        ```
1. マイグレーションファイルの記載とマイグレート
    1. laravel8_crudディレクトリで下記コマンドを実行してモデルファイルとマイグレーションファイルを作成する。

        ```terminal
        $ php artisan make:model ContentImage -m
        ```
    1. laravel8_crudディレクトリで下記コマンドを実行して作成したマイグレーションファイルを開く。

        ```terminal
        $ vi database/migrations/YYYY_MM_DD_HHMMSS_create_content_images_table.php
        ```
    1. up側に下記の内容を記載する。

        ```laravel8_crud/database/migrations/YYYY_MM_DD_HHMMSS_create_content_images_table.php
        $table->integer('content_id');
        $table->string('file_path');
        $table->string('file_name');
        ```
    1. 記載後の全内容を下記に記載する。

        ```laravel8_crud/database/migrations/YYYY_MM_DD_HHMMSS_create_content_images_table.php
        <?php
        
        use Illuminate\Database\Migrations\Migration;
        use Illuminate\Database\Schema\Blueprint;
        use Illuminate\Support\Facades\Schema;
        
        class CreateContentImagesTable extends Migration
        {
            /**
             * Run the migrations.
             *
             * @return void
             */
            public function up()
            {
                Schema::create('content_images', function (Blueprint $table) {
                    $table->id();
                    // 下記を追記する
                    $table->integer('content_id');
                    $table->string('file_path');
                    $table->string('file_name');
                    // 上記までを追記する
                    $table->timestamps();
                });
            }
        
            /**
             * Reverse the migrations.
             *
             * @return void
             */
            public function down()
            {
                Schema::dropIfExists('content_images');
            }
        }
        ```
    1. laravel8_crudディレクトリで下記コマンドを実行してマイグレーションを行う。

        ```terminal
        $ php artisan migrate
        ```
1. コントローラファイルの修正
    1. laravel8_crudディレクトリで下記コマンドを実行してコントローラファイルを開く。

        ```terminal
        $ vi app/Http/Controllers/ContentController.php
        ```
    1. 下記のように修正する。

        ```laravel8_crud/app/Http/Controllers/ContentController.php
        <?php
        
        namespace App\Http\Controllers;
        
        use Illuminate\Http\Request;
        use App\Models\Content;
        // 下記を追記する
        use App\Models\ContentImage;
        use Illuminate\Support\Facades\Storage;
        // 上記までを追記する
        
        class ContentController extends Controller
        {
            public function input()
            {
                return view('contents.input');
            }
        
            public function save(Request $request)
            {
                $input_content = new Content();
                $input_content->content = $request['content'];
                $input_content->save();
        
                // 下記を追記する
                if ($request->file('file')) {
                    $this->validate($request, [
                        'file' => [
                            // 空でないこと
                            'required',
                            // アップロードされたファイルであること
                            'file',
                            // 画像ファイルであること
                            'image',
                            // MIMEタイプを指定
                            'mimes:jpeg,png',
                        ]
                    ]);
            
                    if ($request->file('file')->isValid([])) {
                        $file_name = $request->file('file')->getClientOriginalName();
                        $file_path = Storage::putFile('/images', $request->file('file'), 'public');
                        
                        $image_info = new ContentImage();
                        $image_info->content_id = $input_content->id;
                        $image_info->file_name = $file_name;
                        $image_info->file_path = $file_path;
                        $image_info->save();
                    }
                }
                // 上記までを追記する
                return redirect(route('output'));
            }
        
            public function output()
            {
                $contents_get_query = Content::select('*');
                $items = $contents_get_query->get();
        
                // 下記を追記する
                foreach ($items as &$item) {
                    $file_path = ContentImage::select('file_path')
                    ->where('content_id', $item['id'])
                    ->first();
                    if (isset($file_path)) {
                        $item['file_path'] = $file_path['file_path'];
                    }
                }
                // 上記までを追記する
            
                return view('contents.output', [
                    'items' => $items,
                ]);
            }
        
            public function detail($content_id)
            {
                $content_get_query = Content::select('*');
                $item = $content_get_query->find($content_id);
        
                // 下記を追記する
                $file_path = ContentImage::select('file_path')
                ->where('content_id', $item['id'])
                ->first();
                if (isset($file_path)) {
                    $item['file_path'] = $file_path['file_path'];
                }
                // 上記までを追記する
        
                return view('contents.detail', [
                    'item' => $item,
                ]);
            }
        
            public function edit($content_id)
            {
                $content_get_query = Content::select('*');
                $item = $content_get_query->find($content_id);
        
                return view('contents.edit', [
                    'item' => $item,
                ]);
            }
        
            public function update(Request $request)
            {
                $content_get_query = Content::select('*');
                $content_info = $content_get_query->find($request['id']);
                $content_info->content = $request['content'];
                $content_info->save();
                return redirect(route('output'));
            }
        
            public function delete(Request $request)
            {
                $contents_delete_query = Content::select('*');
                $contents_delete_query->find($request['id']);
                $contents_delete_query->delete();
        
                // 下記を追記
                $content_images_delete_query = ContentImage::select('*');
                if ($content_images_delete_query->find($request['id'] !== '[]')) {
                    $content_images_delete_query->delete();
                }
                // 上記までを追記
        
                return redirect(route('output'));
            }
        }
        ```
1. ビューファイルの編集
    1. laravel8_crudディレクトリで下記コマンドを実行してビューファイルを開く。

        ```terminal
        $ vi resources/views/contents/input.blade.php
        ```
    1. 下記のように編集する。

        ```laravel8_crud/resources/views/contents/input.blade.php
        @extends('layouts.app')
        
        @section('content')
        
        <h1>input</h1>
        
        {{-- 下記を修正する --}}
        <form action="{{route('save')}}" method="post" enctype="multipart/form-data">
            @csrf
            <textarea name="content" cols="30" rows="10"></textarea>
            {{-- 下記を追記する --}}
            <br>
            @error('file')
                {{$message}}
                <br>
            @enderror
            <input type="file" name="file">
            {{-- 上記までを追記する --}}
            <input type="submit" value="送信">
        </form>
        
        @endsection
        ```
    1. laravel8_crudディレクトリで下記コマンドを実行してビューファイルを開く。

        ```terminal
        $ vi resources/views/contents/output.blade.php
        ```
    1. 下記のように編集する。

        ```laravel8_crud/resources/views/contents/output.blade.php
        @extends('layouts.app')
        
        @section('content')
        
        <h1>output</h1>
        
        @foreach ($items as $item)
        <hr>
        {{-- 下記を追記する --}}
        @if (isset($item['file_path']))
        <img src="{{asset('storage/' . $item['file_path'])}}" alt="{{asset('storage/' . $item['file_path'])}}">
        @endif
        {{-- 上記までを追記する --}}
        <p>{{$item['content']}}</p>
        <a href="{{route('detail', ['content_id' => $item['id']])}}">詳細</a>
        <a href="{{route('edit', ['content_id' => $item['id']])}}">編集</a>
        @endforeach
        
        @endsection
        ```
    1. laravel8_crudディレクトリで下記コマンドを実行してビューファイルを開く。

        ```terminal
        $ vi resources/views/contents/detail.blade.php
        ```
    1. 下記のように編集する。

        ```laravel8_crud/resources/views/contents/detail.blade.php
        @extends('layouts.app')
        
        @section('content')
        
        <h1>detail</h1>
        
        {{-- 下記を追記する --}}
        @if (isset($item['file_path']))
        <img src="{{asset('storage/' . $item['file_path'])}}" alt="{{asset('storage/' . $item['file_path'])}}">
        @endif
        {{-- 上記までを追記する --}}
        <p>投稿ID: {{$item['id']}}</p>
        <p>投稿内容: {{$item['content']}}</p>
        <p>投稿時間: {{$item['created_at']}}</p>
        <a href="{{route('edit', ['content_id' => $item['id']])}}">編集</a>
        <form action="{{route('delete')}}" method="post">
            @csrf
            <input type="hidden" name="id" value="{{$item['id']}}">
            <input type="submit" value="削除">
        </form>
        
        @endsection
        ```
1. 確認
    1. laravel8_crudディレクトリで下記コマンドを実行してローカルサーバを起動する。

        ```terminal
        $ php artisan serve
        ```
    1. ブラウザで下記にアクセスする。
        - [http://127.0.0.1:8000/input](http://127.0.0.1:8000/input)
    1. コメントを入力後に「ファイルを選択」をクリックしてjpegかpngファイルを選択して「送信」をクリックする。

        ![laravel8_crud-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/c886425b-99b1-f503-a09c-d93585354baf.png)

    1. 下記にアクセスする。
        - [http://127.0.0.1:8000/output](http://127.0.0.1:8000/output)
    1. 画像が表示されることを確認する。
    1. 詳細をクリックする。

        ![laravel8_crud-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/633fd081-f82a-76e6-d49a-d888c44557ca.png)

    1. 投稿内容の詳細とともに画像が表示されることを確認する。

        ![laravel8_crud-4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/ad148621-bd1c-b533-d207-bf9addf3e321.png)