🌊

Laravel 10 + TinyMCE で画像アップロード

2023/05/28に公開2

概要

ここでは、Laravel 10とTinyMCEを使って画像をアップロードできる機能を実装したことについて記載しています。
実際に上記の実装について調べてみましたが、なかなか記事が見つからず苦戦をしたので、他の方のご参考になれば幸いです。

TinyMCEについて

WYSIWYGと呼ばれるエディタの一つで、テキストエリアを見たまま編集ができるもので無料で実装ができるものとなります。
https://www.tiny.cloud/

利用イメージ

  • WordPressのようなCMSを自身で構築する場合など、記事作成部分にWYSIWYGエディタを用いたテキストエリアを実装できる
  • WYSIWYGエディタを使った画像アップロード機能を実装できる

開発環境

  • XAMPP v3.3.0
  • composer 2.5.5
  • VS Code

利用言語

  • PHP 8.2.0
  • Laravel 10.12.0

実装方法

ここからは実際に実装していく方法を記載していきたいと思います。

参考サイト

https://www.dbestech.com/tutorials/easiest-way-to-upload-image-in-laravel

github

https://github.com/DaiNaka1207/editor-app

プロジェクトの新規作成

editor-appというプロジェクトを作成していきます
コマンドプロンプトにて下記のコマンドを実行していきます。
composer global require laravel/installer
laravel new editor-app
cd editor-app

環境設定

.env
- APP_NAME=laravel
+ APP_NAME=editor-app
config/app.php
- 'timezone' => 'UTC',
+ 'timezone' => 'Asia/Tokyo',

- 'locale' => 'en',
+ 'locale' => 'ja',

- 'faker_locale' => 'en_US',
+ 'faker_locale' => 'ja_JP',

モデルの作成

コマンドプロンプトにて下記のコマンドを実行します。
php artisan make:model post -crm

データベースの構築

データベースを作成します。
コマンドプロンプトにて下記を実行していきます。

C:\xampp\mysql\bin\mysql.exe -u user
create database editor_app

次に、カラムを設定していきます。
マイグレーションファイルを編集していきます。
Path:editor-app\database\migrations\yyyy_mm_dd_HHMMSS_create_posts_table.php

yyyy_mm_dd_HHMMSS_create_posts_table.php
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
+           $table->text('contents');
            $table->timestamps();
        });

最後にデータベースに反映させていきます。
コマンドプロンプトにて次のコマンドを実行します。
php artisan migrate
postsテーブルの構成を確認すると下記のようになっていればデータベースは完成です。

+------------+---------------------+------+-----+---------+----------------+
| Field      | Type                | Null | Key | Default | Extra          |
+------------+---------------------+------+-----+---------+----------------+
| id         | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| contents   | text                | YES  |     | NULL    |                |
| created_at | timestamp           | YES  |     | NULL    |                |
| updated_at | timestamp           | YES  |     | NULL    |                |
+------------+---------------------+------+-----+---------+----------------+

閲覧ページの作成

初めのページにはデータベースに保存されたcontents部分を表示させるためのページを作成していきます。
Path:editor-app\resources\views\post\index.blade.php

index.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>{{config('app.name')}}</title>
</head>
<body>
    <h1>{{config('app.name')}}</h1>
    <hr>

    {!! $post->contents !!}

    <hr>
    <a href="{{route('post.edit', ['post' => $post->id])}}">編集</a>

</body>
</html>

次にルート設定をしていきます。
Path:editor-app\routes\web.php

web.php
use App\Http\Controllers\PostController;

Route::get('/', function () {
-   return view('welcome');
+   return redirect()->route('post.show', ['post' => 1]);
});

+ Route::resource('post', PostController::class);

最後にコントローラーに処理を書いていきます。

PostController.php
    public function show(post $post)
    {
-       //
+       return view('post.index', compact('post'));
    }

編集ページの作成

編集画面を作成していきます。
初めは単純にテキストエリアだけを設置します。

Path:editor-app\resources\views\post\edit.blade.php

edit.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>{{config('app.name')}}</title>

    <style>
        ul {list-style: none; padding-left: 0;}
    </style>
</head>
<body>
    <h1>{{config('app.name')}}</h1>

    <form action="{{route('post.update', ['post' => $post->id])}}" method="POST">
        @csrf @method('PUT')
        <ul>
            <li><textarea name="contents" cols="30" rows="10">{{$post->contents}}</textarea></li>
            <li><button type="submit">送信</button></li>
        </ul>
    </form>

</body>
</html>

次にコントローラーに処理を書いていきます。

PostController.php
    public function edit(post $post)
    {
-       //
+       return view('post.edit', compact('post'));
    }

    public function update(Request $request, post $post)
    {
-       //
+       $post->fill($request->all())->save();
+       return view('post.index', compact('post'));
    }

TinyMCEの設定

TinyMCEの公式にあるLaravelへの導入に関する記事を参考に設定していきます。

まずは、TinyMCEのconfig設定が含まれたコンポーネントを作成していきます。
コマンドプロンプトで次のコマンドを実行します。
php artisan make:component Head/tinymceConfig

次の場所にファイルが作成されます。
editor-app\app\View\Components\Head\tinymceConfig.php
editor-app\resources\views\components\head\tinymce-config.blade.php

二つ目のブレードファイルの方を次の通りに編集します。

TinyMCEのAPIキーについて

https://www.tiny.cloud/
公式ページにてFreePlanにご登録いただきAPIキーを取得することができます。
なお、APIキーの取得にクレジットカードの入力等は不要なため、ご心配は要りません。

下記のコードは、下記の記事を参考に、イメージ貼り付けの処理のところは、同じ感じにしています。
https://www.dbestech.com/tutorials/easiest-way-to-upload-image-in-laravel

tinymce-config.blade.php
<script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/6/tinymce.min.js" referrerpolicy="origin"></script> // 👈no-api-keyのところはご自身のAPIキーをご用意ください

<script>
    tinymce.init({
        selector: 'textarea#editor',
        language: 'ja',
        icons: 'thin',
        statusbar: false,
        menubar: false,
        plugins: 'anchor autolink charmap codesample emoticons image link lists media searchreplace table visualblocks wordcount code',
        toolbar: 'blocks bold italic underline strikethrough forecolor removeformat | emoticons link image table | numlist bullist | code',
        block_formats: 'Paragraph=p; Header 1=h3; Header 2=h4; Header 3=h5',
        color_map: [
            '#BFEDD2', 'Light Green',
            '#FBEEB8', 'Light Yellow',
            '#F8CAC6', 'Light Red',
            '#ECCAFA', 'Light Purple',
            '#C2E0F4', 'Light Blue',
            
            '#2DC26B', 'Green',
            '#F1C40F', 'Yellow',
            '#E03E2D', 'Red',
            '#B96AD9', 'Purple',
            '#3598DB', 'Blue',
            
            '#169179', 'Dark Turquoise',
            '#E67E23', 'Orange',
            '#BA372A', 'Dark Red',
            '#843FA1', 'Dark Purple',
            '#236FA1', 'Dark Blue',
            
            '#ECF0F1', 'Light Gray',
            '#CED4D9', 'Medium Gray',
            '#95A5A6', 'Gray',
            '#7E8C8D', 'Dark Gray',
            '#34495E', 'Navy Blue',
            
            '#000000', 'Black',
            '#ffffff', 'White',
        ],

        // ここから下がイメージの貼り付けをするための処理
        image_title: true,
        automatic_uploads: true,
        images_upload_url: '/upload', // 画像が貼り付けられるとアクセスするリンク
        file_picker_types: 'image',
        file_picker_callback: function(cb, value, meta) {
            var input = document.createElement('input');
            input.setAttribute('type', 'file');
            input.setAttribute('accept', 'image/*');
            input.onchange = function() {
                var file = this.files[0];

                var reader = new FileReader();
                reader.readAsDataURL(file);
                reader.onload = function () {
                    var id = 'blobid' + (new Date()).getTime();
                    var blobCache =  tinymce.activeEditor.editorUpload.blobCache;
                    var base64 = reader.result.split(',')[1];
                    var blobInfo = blobCache.create(id, file, base64);
                    blobCache.add(blobInfo);
                    cb(blobInfo.blobUri(), { title: file.name });
                };
            };
            input.click();
        }
    });
</script>

上記コードの画像が貼り付けられるとアクセスするリンクにあわせてルート設定していきます。

web.php
+ // イメージが貼り付けられた際の処理
+ Route::post('/upload', [PostController::class, 'upload']);

次は、コントローラーに画像貼り付け後の処理を書いていきます。

PostController.php
+    public function upload()
+    {
+        $fileName = $request->file('file')->getClientOriginalName();
+        $path = $request->file('file')->store('upload', 'public');
+        return response()->json(['location'=>"/storage/$path"]);
+    }

/uploadのリンクに飛んだ後にCSRFにならないように次のように編集していきます。
path:editor-app\app\Http\Middleware\VerifyCsrfToken.php

VerifyCsrfToken.php
    protected $except = [
-       //
+       '/upload'
    ];

最後に、保存先であるstorageの中にシンボリックを作成します。
コマンドプロンプトにて下記のコマンドを実行します。
php artisan storage:link

デモサイト

作成したデモサイトは下記にデプロイしてあります。
ご自由にテストしてください。
https://editor-app.dainaka.live

Discussion

Namishi@CriativeUnitHystericEnd.Namishi@CriativeUnitHystericEnd.

貴重な記事まことにありがとうございます。
ご質問があり、コメントいたしました。

画像のアップロードフォルダをstorageフォルダ内に収納したい場合の指定はどのようにするのでしょうか?
お忙しい中大変申し訳ございませんが、ご教授いただければ幸いです。

DaiNakaDaiNaka

Namishi@CriativeUnitHystericEnd.さん
まずは、本記事をご参考にしていただき有難うございます。

そして、ご質問いただいた内容について回答ですが、結論から言いますと storageフォルダに直接保存する方法はありません。
正確には、私自身が回答を持ち合わせていません。勉強不足でご期待に応えられず申し訳ないです。

実際にはできる方法はあるかと思いますが、そもそもpublicフォルダに保存されていないファイルは公開されていない=表示できない。ということから、最後にstorageフォルダからpublicフォルダにシンボリックリンクを張っていることもあり、publicフォルダに保存されたファイルをstorageフォルダからアクセスができるようにしていると解釈しています。