Laravel 10 + TinyMCE で画像アップロード
概要
ここでは、Laravel 10とTinyMCEを使って画像をアップロードできる機能を実装したことについて記載しています。
実際に上記の実装について調べてみましたが、なかなか記事が見つからず苦戦をしたので、他の方のご参考になれば幸いです。
TinyMCEについて
WYSIWYGと呼ばれるエディタの一つで、テキストエリアを見たまま編集ができるもので無料で実装ができるものとなります。
利用イメージ
- WordPressのようなCMSを自身で構築する場合など、記事作成部分にWYSIWYGエディタを用いたテキストエリアを実装できる
- WYSIWYGエディタを使った画像アップロード機能を実装できる
開発環境
- XAMPP v3.3.0
- composer 2.5.5
- VS Code
利用言語
- PHP 8.2.0
- Laravel 10.12.0
実装方法
ここからは実際に実装していく方法を記載していきたいと思います。
参考サイト
github
プロジェクトの新規作成
editor-app
というプロジェクトを作成していきます
コマンドプロンプトにて下記のコマンドを実行していきます。
composer global require laravel/installer
laravel new editor-app
cd editor-app
環境設定
- APP_NAME=laravel
+ APP_NAME=editor-app
- '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
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
<!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
use App\Http\Controllers\PostController;
Route::get('/', function () {
- return view('welcome');
+ return redirect()->route('post.show', ['post' => 1]);
});
+ Route::resource('post', PostController::class);
最後にコントローラーに処理を書いていきます。
public function show(post $post)
{
- //
+ return view('post.index', compact('post'));
}
編集ページの作成
編集画面を作成していきます。
初めは単純にテキストエリアだけを設置します。
Path:editor-app\resources\views\post\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>
次にコントローラーに処理を書いていきます。
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キーについて
なお、APIキーの取得にクレジットカードの入力等は不要なため、ご心配は要りません。
下記のコードは、下記の記事を参考に、イメージ貼り付けの処理のところは、同じ感じにしています。
<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>
上記コードの画像が貼り付けられるとアクセスするリンク
にあわせてルート設定していきます。
+ // イメージが貼り付けられた際の処理
+ Route::post('/upload', [PostController::class, 'upload']);
次は、コントローラーに画像貼り付け後の処理を書いていきます。
+ 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
protected $except = [
- //
+ '/upload'
];
最後に、保存先であるstorage
の中にシンボリックを作成します。
コマンドプロンプトにて下記のコマンドを実行します。
php artisan storage:link
デモサイト
作成したデモサイトは下記にデプロイしてあります。
ご自由にテストしてください。
Discussion
貴重な記事まことにありがとうございます。
ご質問があり、コメントいたしました。
画像のアップロードフォルダをstorageフォルダ内に収納したい場合の指定はどのようにするのでしょうか?
お忙しい中大変申し訳ございませんが、ご教授いただければ幸いです。
Namishi@CriativeUnitHystericEnd.さん
まずは、本記事をご参考にしていただき有難うございます。
そして、ご質問いただいた内容について回答ですが、結論から言いますと storageフォルダに直接保存する方法はありません。
正確には、私自身が回答を持ち合わせていません。勉強不足でご期待に応えられず申し訳ないです。
実際にはできる方法はあるかと思いますが、そもそもpublicフォルダに保存されていないファイルは公開されていない=表示できない。ということから、最後にstorageフォルダからpublicフォルダにシンボリックリンクを張っていることもあり、publicフォルダに保存されたファイルをstorageフォルダからアクセスができるようにしていると解釈しています。