ポートフォリオに画像投稿機能を実装しました。
画像ファイルをAWSのS3に保存する際に、いくつか躓いた点があったので、それをアウトプットしようと思います。
#バージョン
PHP 7.4.2
Laravel 6.20.26
Docker 20.10.6
docker-compose 1.29.1
OS Windows
#編集前のコード
画像ファイルをS3に保存する前はpublicフォルダに保存していました。
「S3に保存する前に、とりあえず画像機能を実装したい」という方は、下記のコードをご参照ください。
画像投稿に該当する箇所のみ記載しています。
また、【Laravel 7.x】Laravelで画像投稿機能を実装を参考にしつつ、コーディングしています。
//略
Route::get('/', 'ArticleController@index')->name('articles.index');
Route::resource('/articles', 'ArticleController')->except(['index', 'show'])->middleware('auth');
Route::resource('/articles', 'ArticleController')->only(['show']);
//略
//略
class Article extends Model
{
protected $fillable = [
'title',
'body',
];
//略
//略
public function rules()
{
return [
'image' => 'mimes:jpeg,jpg,png,gif|max:10240',
'title' => 'required|max:50',
'body' => 'required|max:500',
'tags' => 'json|regex:/^(?!.*\s).+$/u|regex:/^(?!.*\/).*$/u',
];
}
//略
//略
public function store(ArticleRequest $request, Article $article)
{
$article->fill($request->all());
if ($request->file('image')){
$filename = $request->file('image')->store('public'); // publicフォルダに保存
$article->image = str_replace('public/', '', $filename); // 保存するファイル名からpublicを除外
}
$article->user_id = $request->user()->id;
$article->save();
//略
return redirect()->route('articles.index');
}
//略
//略
public function up()
{
Schema::create('articles', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('title');
$table->text('body');
$table->bigInteger('user_id')->unsigned();
$table->timestamps();
});
}
//略
//略
class AddImageToArticlesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('articles', function (Blueprint $table) {
$table->string('image')->nullable()->after('id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('articles', function (Blueprint $table) {
$table->dropColumn('image');
});
}
}
//略
<img src="{{ asset('/storage/'.$article->image)}}" alt="">
//略
//略
<form method="POST" action="{{ route('articles.store') }}" enctype="multipart/form-data">
@csrf
<div class="container">
<div class="row">
<div class="offset-md-2 col-md-8">
//略
<div class="form-group">
<input type="file" class="from-control-file" id="image" name="image">
</div>
</div>
</div>
</div>
<button type="submit" class="btn aqua-gradient btn-block"><i class="fas fa-pen mr-1"></i>投稿する</button>
</form>
//略
#事前準備
S3に保存するためには、事前にAWSの方でIAMユーザーやS3バケットの設定が必要になります。
Rails, Laravel(画像アップロード)向けAWS(IAM:ユーザ, S3:バケット)の設定の手順で進めてください。(この記事とてもわかりやすかったです!)
#編集箇所
LaravelでAWS S3へ画像をアップロードするを参考にしつつ、コードを修正しました。(この記事も非常にわかりやすかったです!)
//略
public function store(ArticleRequest $request, Article $article)
{
$article->fill($request->all());
if ($request->file('image')){
//==========ここから削除==========
$filename = $request->file('image')->store('public'); // publicフォルダに保存
$article->image = str_replace('public/', '', $filename); // 保存するファイル名からpublicを除外
//==========ここまで削除==========
//==========ここから追記==========
// s3アップロード開始
$image = $request->file('image');
// バケットの`myprefix`フォルダへアップロード
$path = Storage::disk('s3')->putFile('myprefix', $image, 'public');
// アップロードした画像のフルパスを取得
$article->image_path = Storage::disk('s3')->url($path);
//==========ここまで追記==========
}
$article->user_id = $request->user()->id;
$article->save();
//略
return redirect()->route('articles.index');
}
//略
//略
<img src="{{ asset('/storage/'.$article->image)}}" alt=""> //この行を削除
<img src="{{ $article->image_path }}"> //この行を追記
//略
#画像ファイルの保存先をAWS S3に変更した際のエラー
###Class 'App\Http\Controllers\Storage' not found
一番最初のエラーはこれでした。
Storageが見つからないと言われています。
ArticleController.phpにuse Illuminate\Support\Facades\Storage;
を追記することで、storage facadeをインポートし、このエラーは解決しました。
use Storage;
でも試してみましたが、問題なく動作しました。
###Class 'League\Flysystem\AwsS3v3\AwsS3Adapter' not found
次のエラーはこれです。
単純にflysystem-aws-s3-v3のインストールを忘れていました。
ドキュメントにもちゃんと書いてありますね。(Laravel 6.x ファイルストレージ)
$ composer require league/flysystem-aws-s3-v3
でインストールできます。
ただし、バージョンの指定がないと2.0がインストールされてエラーになることもあるらしいので、$ composer require league/flysystem-aws-s3-v3 ~1.0
にしておいた方が安心ですね。
私の場合はDockerのappコンテナの中で実行したかったので、$ docker-compose exec app composer require league/flysystem-aws-s3-v3 ~1.0
でインストールしました。
###SQLSTATE[42S22]: Column not found: 1054 Unknown column 'image_path' in 'field list'
最後のエラーはこれです。
「articlesテーブルにimage_pathカラムがない」と言われています。
mysqlでarticlesテーブルのカラムを確認すると、、、
mysql> show columns from articles;
+------------+-----------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+-----------------+------+-----+---------+----------------+
| id | bigint unsigned | NO | PRI | NULL | auto_increment |
| image | varchar(255) | YES | | NULL | |
| title | varchar(255) | NO | | NULL | |
| body | text | NO | | NULL | |
| user_id | bigint unsigned | NO | | NULL | |
| created_at | timestamp | YES | | NULL | |
| updated_at | timestamp | YES | | NULL | |
+------------+-----------------+------+-----+---------+----------------+
7 rows in set (0.01 sec)
確かにないですね。
image_pathカラムではなく、imageカラムが正解でした。
下記のように修正しました。
//略
public function store(ArticleRequest $request, Article $article)
{
$article->fill($request->all());
if ($request->file('image')){
// s3アップロード開始
$image = $request->file('image');
// バケットの`myprefix`フォルダへアップロード
$path = Storage::disk('s3')->putFile('myprefix', $image, 'public');
// アップロードした画像のフルパスを取得
$article->image = Storage::disk('s3')->url($path); //この行を修正
}
$article->user_id = $request->user()->id;
$article->save();
//略
return redirect()->route('articles.index');
}
//略
//略
<img src="{{ $article->image }}">
//略
#バケットを確認
これで無事にS3へ保存することができるようになりました。
念のため実際にアップロードして、バケットへ画像がアップロードできているか確認しました。
ちゃんと保存できていますね!